feat: add Chinese (简体中文) i18n support#1518
Conversation
- Add lightweight i18n infrastructure (React Context + useT hook, zero extra deps) - Add Redux locale slice with browser language auto-detection and persistence - Add language switcher dropdown in Settings > General - Translate all UI strings across 80+ components to simplified Chinese - 731 translation keys covering navigation, settings, chat, memory, onboarding, etc. - English fallback for missing keys; locale persisted in localStorage
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds I18nProvider and useT hook, English and Simplified Chinese translation maps, a persisted locale slice, wraps App in I18nProvider, and replaces hard-coded UI strings with t(...) across components, pages, onboarding, settings, intelligence, and features. ChangesI18n plumbing and app wiring
UI localization sweep
Sequence Diagram(s): Estimated code review effort: Possibly related PRs
Suggested reviewers
|
There was a problem hiding this comment.
Actionable comments posted: 6
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
app/src/pages/Notifications.tsx (2)
27-36: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winLocalize the relative time formatting.
The
formatTimefunction returns hardcoded English strings ('just now', 'm ago', 'h ago', 'd ago') that will not translate. Consider using a localized relative time formatter or translation keys with interpolation, e.g.,t('time.minutesAgo', { count: min }).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Notifications.tsx` around lines 27 - 36, The formatTime function currently returns hardcoded English phrases; update it to produce localized output by replacing those string literals with a locale-aware approach—either use your i18n translation function with plural/interpolation keys (e.g. t('time.justNow'), t('time.minutesAgo', { count: min }), t('time.hoursAgo', { count: hr }), t('time.daysAgo', { count: d })) or use Intl.RelativeTimeFormat with the current locale to format minutes/hours/days. Change the implementation inside formatTime to call the chosen localization helper (e.g. t(...) or rtf.format(...)) and return those localized strings instead of the hardcoded 'just now', `${min}m ago`, `${hr}h ago`, `${d}d ago`.
17-25: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winLocalize the notification category labels.
The
CATEGORY_LABELmapping contains hardcoded English strings that will display incorrectly for non-English locales. Replace with translation keys, e.g.:const CATEGORY_LABEL: Record<NotificationCategory, string> = { messages: t('alerts.categories.messages'), agents: t('alerts.categories.agents'), // ... etc };Move this inside the component to access
t, or convert to a function that acceptst.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Notifications.tsx` around lines 17 - 25, The CATEGORY_LABEL constant currently contains hardcoded English strings; move it inside the Notifications component (or replace it with a helper function like getCategoryLabel(t: TFunction): Record<NotificationCategory,string>) so you can call the i18n translator `t` for each key (e.g. use t('alerts.categories.messages'), t('alerts.categories.agents'), etc.) and update all usages to read from the localized mapping returned by getCategoryLabel or the in-component CATEGORY_LABEL so NotificationCategory labels render per locale.app/src/components/settings/panels/TeamManagementPanel.tsx (1)
128-128: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winLocalize the dynamic title with interpolation.
The title
Manage ${team.name}cannot be localized in its current form because the template literal is hardcoded in English. Use a translation key with parameter substitution, e.g.,t('team.manageTitle', { name: team.name })(assuming your i18n implementation supports interpolation).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/TeamManagementPanel.tsx` at line 128, The title prop in TeamManagementPanel is hardcoded as a template literal `Manage ${team.name}`, which prevents localization; replace it with a translated string that interpolates the team name (for example call your i18n function `t` with a key like `team.manageTitle` and pass `{ name: team.name }`), update the component where `title={`Manage ${team.name}`}` is used to `title={t('team.manageTitle', { name: team.name })}`, and ensure the i18n resource contains the corresponding `team.manageTitle` entry (e.g., "Manage {{name}}") so interpolation works.app/src/features/human/MicCloudComposer.tsx (1)
186-237:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winComplete localization for all user-facing error paths in this composer.
Some errors are translated, but others are still hardcoded (e.g., stop/finalize/transcription failures), which creates mixed-language UX.
Also applies to: 268-313
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/features/human/MicCloudComposer.tsx` around lines 186 - 237, Several user-facing error messages in MicCloudComposer are hardcoded and must be fully localized; update every error path (including the getUserMedia catch, MediaRecorder creation catch, stop/finalize/transcription failures referenced around the recorder lifecycle and lines ~268-313) to use the translation function t(...) instead of raw strings. Locate uses of onError, composerLog, startInFlightRef/disposedRef handling, and the MediaRecorder-related blocks (e.g., where pickRecorderMime() is used and where errors are formatted like `${t('mic.failedToStartRecorder')}: ${msg}`) and replace any remaining hardcoded messages with appropriate t('mic.<key>') keys (adding new keys if needed) while preserving the appended error details; ensure every call to onError passes a translated string and that composerLog entries remain informative but user-facing notifications come only from t(...) values.app/src/components/intelligence/SubconsciousReflectionCards.tsx (1)
46-53:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize remaining card metadata labels to avoid mixed-language output.
KIND_LABELvalues and relative-time strings are still hardcoded English, so zh-CN users will see partially untranslated cards.Also applies to: 62-70, 83-83
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/SubconsciousReflectionCards.tsx` around lines 46 - 53, KIND_LABEL currently contains hardcoded English labels for ReflectionKind and the component also renders hardcoded relative-time strings; replace each hardcoded label in KIND_LABEL with calls to the app's i18n translation function (e.g., t('reflection.kind.hotness_spike') etc.) keyed by ReflectionKind, and change any manual relative-time text generation to use the app's localization/Intl utilities (e.g., Intl.RelativeTimeFormat or the project's i18n helper) so both card labels and relative-time strings are rendered through the locale-aware translation/formatters.app/src/components/intelligence/MemoryHeatmap.tsx (1)
14-15:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove hardcoded English locale/date labels in the heatmap.
Day labels and date/month formatting are still forced to English, so the component remains partially untranslated in zh-CN.
Also applies to: 36-43, 103-106, 244-248
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/MemoryHeatmap.tsx` around lines 14 - 15, The component currently uses hardcoded English strings (e.g., the DAY_LABELS constant) and manual date/month formatting; replace those with locale-aware formatting using Intl.DateTimeFormat (or the app's i18n locale getter) so day names and date/month strings are generated for the active locale (fallback to navigator.language if none). Specifically, remove the DAY_LABELS constant and any other hardcoded weekday/month strings in MemoryHeatmap.tsx and instead generate labels by calling new Intl.DateTimeFormat(locale, { weekday: 'short' }) and new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric' }) (or the equivalent used across the component) where labels/hover text are produced; ensure the locale is passed in or read from the app context so zh-CN and other locales render correctly.app/src/components/intelligence/WhatsAppMemorySection.tsx (1)
79-85: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winIncomplete i18n:
relativeTimefunction returns hardcoded English strings.The
relativeTimehelper returns hardcoded English time labels ('just now','m ago','h ago','d ago'), but these should be localized to match the component's i18n migration.🌐 Proposed fix to localize time strings
Pass the
tfunction torelativeTimeand use translation keys:-function relativeTime(secs: number): string { +function relativeTime(secs: number, t: (key: string) => string): string { const delta = Date.now() / 1000 - secs; - if (delta < 60) return 'just now'; - if (delta < 3600) return `${Math.floor(delta / 60)}m ago`; - if (delta < 86400) return `${Math.floor(delta / 3600)}h ago`; - return `${Math.floor(delta / 86400)}d ago`; + if (delta < 60) return t('time.justNow'); + if (delta < 3600) return t('time.minutesAgo').replace('{0}', String(Math.floor(delta / 60))); + if (delta < 86400) return t('time.hoursAgo').replace('{0}', String(Math.floor(delta / 3600))); + return t('time.daysAgo').replace('{0}', String(Math.floor(delta / 86400))); }Then update the call site on line 59:
- {lastSyncTs !== null && <> · {relativeTime(lastSyncTs)}</>} + {lastSyncTs !== null && <> · {relativeTime(lastSyncTs, t)}</>}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/WhatsAppMemorySection.tsx` around lines 79 - 85, The relativeTime helper currently returns hardcoded English strings; change the signature of relativeTime (e.g., relativeTime(secs: number, t: TFunction)) to accept the i18n translator and replace the hardcoded strings with translation keys (e.g., t('relative.justNow'), t('relative.minutesAgo', {count}), etc.), then update the WhatsAppMemorySection call site(s) where relativeTime is used to pass the component's t function (from useTranslation) into relativeTime so all labels are localized.app/src/components/settings/panels/AboutPanel.tsx (1)
97-126: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winIncomplete i18n:
summaryForfunction returns hardcoded English strings.The
summaryForhelper returns all update status messages in hardcoded English ('Contacting the update server…','You are running the latest version.', etc.), which will not be localized for Chinese users despite the surrounding UI being translated.🌐 Proposed fix to localize status messages
Update the function signature to accept the
tfunction and use translation keys:-function summaryFor( +function summaryFor( + t: (key: string, replacements?: Record<string, string>) => string, phase: ReturnType<typeof useAppUpdate>['phase'], info: ReturnType<typeof useAppUpdate>['info'], error: string | null ): string { switch (phase) { case 'checking': - return 'Contacting the update server…'; + return t('settings.about.status.checking'); case 'available': return info?.available_version - ? `Version ${info.available_version} found — downloading in the background…` - : 'A new version was found — downloading…'; + ? t('settings.about.status.availableVersion', { version: info.available_version }) + : t('settings.about.status.available'); case 'downloading': - return 'Downloading the latest version in the background…'; + return t('settings.about.status.downloading'); case 'ready_to_install': return info?.available_version - ? `Version ${info.available_version} is downloaded and ready. Use the prompt at the bottom right to restart.` - : 'A new version is downloaded and ready. Restart to apply.'; + ? t('settings.about.status.readyVersion', { version: info.available_version }) + : t('settings.about.status.ready'); case 'installing': - return 'Installing the update…'; + return t('settings.about.status.installing'); case 'restarting': - return 'Relaunching with the new version…'; + return t('settings.about.status.restarting'); case 'up_to_date': - return 'You are running the latest version.'; + return t('settings.about.status.upToDate'); case 'error': - return error ?? 'Last update check failed. Try again in a moment.'; + return error ?? t('settings.about.status.error'); default: - return 'Click "Check for updates" to look for a newer version.'; + return t('settings.about.status.default'); } }Then update the call site on line 27:
- const summary = summaryFor(phase, info, error); + const summary = summaryFor(t, phase, info, error);Note: If your i18n implementation supports interpolation with a different syntax (e.g.,
{version}placeholders), adjust the translation key usage accordingly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/AboutPanel.tsx` around lines 97 - 126, The summaryFor helper currently returns hardcoded English strings; change its signature to accept the i18n translator (e.g., add a parameter t: (key: string, opts?: any) => string) and replace each hardcoded message with calls to t using appropriate translation keys (and interpolation for info?.available_version where needed), then update every call site of summaryFor (the place that passes useAppUpdate()'s phase and info) to pass the t function as the new first/last argument so messages are localized; keep the existing phase and error handling logic (e.g., for the 'error' case use error ?? t('about.update.check_failed')) and pick consistent translation keys for all branches.app/src/components/settings/panels/LocalModelPanel.tsx (1)
292-329: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winIncomplete i18n: Usage feature labels and hints remain hardcoded.
The usage feature checkboxes (Embeddings, Heartbeat, Learning/reflection, Subconscious) have hardcoded English labels and hints, inconsistent with the surrounding i18n migration.
🌐 Proposed fix to localize feature labels
<div className={`space-y-2 pl-6 ${usageFlags.runtime_enabled ? '' : 'opacity-50'}`}> {( [ { key: 'usage_embeddings' as const, - label: 'Embeddings', - hint: 'Generate memory embeddings locally instead of in the cloud.', + label: t('localModel.embeddings'), + hint: t('localModel.embeddingsDesc'), }, { key: 'usage_heartbeat' as const, - label: 'Heartbeat', - hint: 'Run heartbeat reasoning locally.', + label: t('localModel.heartbeat'), + hint: t('localModel.heartbeatDesc'), }, { key: 'usage_learning_reflection' as const, - label: 'Learning / reflection', - hint: 'Run learning and reflection passes locally.', + label: t('localModel.learningReflection'), + hint: t('localModel.learningReflectionDesc'), }, { key: 'usage_subconscious' as const, - label: 'Subconscious', - hint: 'Run subconscious evaluation locally.', + label: t('localModel.subconscious'), + hint: t('localModel.subconsciousDesc'), }, ] as const ).map(({ key, label, hint }) => (🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/LocalModelPanel.tsx` around lines 292 - 329, The checkbox labels and hints in the mapped array are hardcoded English strings; replace them with localized strings using the app's translation function (e.g., t or useTranslation) for each entry so the array entries use i18n keys instead of literal text. Update the array of objects (the one with keys 'usage_embeddings', 'usage_heartbeat', 'usage_learning_reflection', 'usage_subconscious') to set label: t('...') and hint: t('...') (or the equivalent translation hook) and ensure the component still reads those values when rendering and when calling updateUsage and checking usageFlags. Ensure types remain the same (keep the as const) and that translation keys are added to the translations files.app/src/components/settings/panels/PrivacyPanel.tsx (1)
21-27:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the
KIND_LABELconstant.The
KIND_LABELrecord contains hardcoded English strings that are rendered to users in privacy capability badges (line 135). These labels should be localized for consistency with the rest of the component's i18n migration.🌐 Proposed fix to localize data kind labels
Since
KIND_LABELis a module-level constant, you'll need to either:Option 1: Convert to a function that accepts
t:-const KIND_LABEL: Record<PrivacyDataKind, string> = { - raw: 'Raw user content', - derived: 'Derived signals', - credentials: 'Credentials', - diagnostics: 'Diagnostics', - metadata: 'Metadata', -}; +function getKindLabel(kind: PrivacyDataKind, t: ReturnType<typeof useT>['t']): string { + const labels: Record<PrivacyDataKind, string> = { + raw: t('privacy.dataKind.raw'), + derived: t('privacy.dataKind.derived'), + credentials: t('privacy.dataKind.credentials'), + diagnostics: t('privacy.dataKind.diagnostics'), + metadata: t('privacy.dataKind.metadata'), + }; + return labels[kind]; +}Then update the call site at line 135:
- {KIND_LABEL[cap.privacy.data_kind]} + {getKindLabel(cap.privacy.data_kind, t)}Option 2: Move
KIND_LABELinside the component to accesst.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/PrivacyPanel.tsx` around lines 21 - 27, KIND_LABEL currently contains hardcoded English strings; change it so labels are produced via the i18n translator instead. Either (A) replace the module-level KIND_LABEL with a function (e.g., getKindLabel(t): Record<PrivacyDataKind,string>) that returns the localized mapping using the passed-in t, then update the usage in PrivacyPanel where the badge is rendered to call getKindLabel(t)[kind]; or (B) move the existing KIND_LABEL definition inside the PrivacyPanel component (or the function that renders the capability badge) and create the mapping using the component's t function. Update all references to use the new localized mapping (e.g., getKindLabel or in-component KIND_LABEL) so badges render translated strings.app/src/pages/onboarding/steps/SkillsStep.tsx (1)
36-47:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the
statusLabelhelper function.The
statusLabelfunction returns hardcoded English strings ('Connected','Connecting','Error'), but this text is rendered to users at line 131. Since the rest of the component has been migrated to use translation keys viat(...), these status labels should also be localized for consistency.🌐 Proposed fix to localize status labels
-function statusLabel(state: ReturnType<typeof deriveComposioState>): string { +function statusLabel(state: ReturnType<typeof deriveComposioState>, t: ReturnType<typeof useT>['t']): string { switch (state) { case 'connected': - return 'Connected'; + return t('skills.connected'); case 'pending': - return 'Connecting'; + return t('skills.connecting'); case 'error': - return 'Error'; + return t('common.error'); default: return ''; } }Then update the call site at line 125:
- {statusLabel(gmailState) && ( + {statusLabel(gmailState, t) && ( <> <div className={`h-1.5 w-1.5 flex-shrink-0 rounded-full ${statusDotClass(gmailConnection)}`} /> <span className={`flex-shrink-0 text-xs ${statusColor(gmailState)}`}> - {statusLabel(gmailState)} + {statusLabel(gmailState, t)} </span> </> )}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/onboarding/steps/SkillsStep.tsx` around lines 36 - 47, The statusLabel helper returns hardcoded English strings; update it to return localized text by either accepting the i18n translator or returning translation keys. Modify statusLabel (or move it inside SkillsStep) to accept t: TFunction and map the states to t('onboarding.skills.status.connected'), t('onboarding.skills.status.pending'), t('onboarding.skills.status.error') (or return those keys) and then update the call site in SkillsStep to use the translator (e.g., statusLabel(t, deriveComposioState(...)) or t(statusLabel(...))). Ensure you reference the existing symbols statusLabel, deriveComposioState, t, and SkillsStep when making the change.app/src/components/intelligence/MemorySyncConnections.tsx (1)
216-219:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the populated-state section heading too.
The success path still renders hardcoded
"Memory sources"while other branches uset('sync.memorySources'), causing mixed-language output.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/MemorySyncConnections.tsx` around lines 216 - 219, The h3 heading in the MemorySyncConnections component currently uses a hardcoded string "Memory sources" causing mixed-language output; replace that literal with the localized key by calling t('sync.memorySources') (the same translator used elsewhere in this file) inside the h3 element (the element with className "text-sm font-semibold text-stone-700"), and ensure the useTranslation hook or t reference already present in MemorySyncConnections is used or imported if missing so the populated-state heading is fully localized.
🟡 Minor comments (16)
app/src/components/settings/components/SettingsMenuItem.tsx-46-48 (1)
46-48:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd
type="button"on Line 46.Without an explicit type, this defaults to
submitinside forms and can trigger accidental submissions.Suggested fix
if (onClick) { return ( <button + type="button" onClick={onClick} className={`w-full flex items-center justify-between py-3 px-4 bg-white ${borderClasses} hover:bg-stone-50 transition-all duration-200 text-left ${roundedClasses} focus:outline-none focus:ring-0 focus:border-inherit`}> {content} </button> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/components/SettingsMenuItem.tsx` around lines 46 - 48, The button in SettingsMenuItem currently lacks an explicit type so inside forms it defaults to "submit" and can cause accidental form submissions; update the JSX <button> in the SettingsMenuItem component (the element with props onClick, className, borderClasses, roundedClasses) to include type="button" to ensure it does not submit forms when clicked.app/src/components/settings/panels/ComposioTriagePanel.tsx-159-159 (1)
159-159:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the saving state label.
Saving…is still hardcoded and will bypass zh-CN translation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/ComposioTriagePanel.tsx` at line 159, The hardcoded "Saving…" string in ComposioTriagePanel bypasses localization; replace the ternary branch that uses the saving variable so it calls the i18n function instead (e.g. change {saving ? 'Saving…' : t('common.save')} to {saving ? t('common.saving') : t('common.save')}) and add the corresponding translation key "common.saving" (with the ellipsis included if desired) to your locale files (zh-CN and others) so the saving state is translated consistently.app/src/components/settings/panels/ComposioTriagePanel.tsx-103-105 (1)
103-105:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid mixed localized and hardcoded copy in the same sentence.
This renders partially in English for zh-CN users. Put the full sentence into a single translation key (with the env var as a placeholder).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/ComposioTriagePanel.tsx` around lines 103 - 105, Replace the mixed localized + hardcoded sentence in ComposioTriagePanel by adding a single translation key (e.g. "composio.triageDescDisabled") that contains the full sentence with a placeholder for the env var, and then call t('composio.triageDescDisabled', { envVar: 'OPENHUMAN_TRIGGER_TRIAGE_DISABLED' }) instead of concatenating t('composio.triageDesc') and the hardcoded <span>; render the envVar placeholder with the same monospace styling (wrap the interpolated value in <span className="font-mono">) so the entire sentence is localized while preserving the visual style for OPENHUMAN_TRIGGER_TRIAGE_DISABLED.app/src/pages/Intelligence.tsx-161-161 (1)
161-161:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winVerify semantic change from "Soon" to "Beta".
The badge text was changed from
"Soon"tot('misc.beta'), but the property driving it is still namedcomingSoon(line 137). These terms have different meanings:
- "Coming Soon": Feature is planned but not yet available
- "Beta": Feature is available but experimental/unstable
If the Dreams tab is not yet functional, the badge should say "Coming Soon" rather than "Beta". If it is functional but experimental, the property should be renamed to reflect that.
Please verify which semantic is correct and ensure the translation key and property name align.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Intelligence.tsx` at line 161, The badge text was changed to t('misc.beta') while the feature flag is still named comingSoon; decide which semantic is correct for the Dreams tab and make the code consistent: if Dreams is not yet available, change the translation back to a "Coming Soon" key (e.g., t('misc.soon')) so the UI matches the comingSoon boolean; if Dreams is available but experimental, rename the prop comingSoon to isBeta (and update all uses) and keep t('misc.beta') (or add/confirm a matching translation key). Update the component rendering logic in Intelligence.tsx (the badge rendering and the property definition/usage) accordingly so names and translation keys align.app/src/pages/onboarding/steps/LocalAIStep.tsx-114-114 (1)
114-114:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConsider using an actionable button-specific translation key for these CTAs.
Lines 114 and 170 render buttons with
onClick={handleConsent}usingt('onboarding.localAI'). The translation values ("Local AI" in English, "本地 AI" in Chinese) are noun phrases suitable for headings/labels, not call-to-action buttons.Button text should be actionable (e.g., "Try Local AI", "Enable Local AI") rather than a static label. Consider creating dedicated button keys such as
'onboarding.localAIButton'or similar, especially to avoid awkward phrasing in other language translations.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/onboarding/steps/LocalAIStep.tsx` at line 114, The button CTAs in LocalAIStep are using the generic noun translation t('onboarding.localAI') instead of an actionable string; update the two button render sites that call onClick={handleConsent} (both uses of t('onboarding.localAI') in LocalAIStep.tsx) to use a new actionable key like t('onboarding.localAIButton') (or 'onboarding.localAI.enable' / similar), add corresponding entries to the i18n resource files for each locale, and ensure both occurrences (the one around line 114 and the one around line 170) are updated so translations render as verbs like "Try Local AI" / "Enable Local AI" rather than a noun label.app/src/components/settings/panels/VoicePanel.tsx-357-357 (1)
357-357:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the in-progress button labels as well.
Saving…,Starting…, andStopping…are still hardcoded English in this otherwise localized panel.Example fix
- {isSaving ? 'Saving…' : t('voice.saveVoiceSettings')} + {isSaving ? t('common.loading') : t('voice.saveVoiceSettings')} - {isStarting ? 'Starting…' : t('voice.startVoiceServer')} + {isStarting ? t('common.loading') : t('voice.startVoiceServer')} - {isStopping ? 'Stopping…' : t('voice.stopVoiceServer')} + {isStopping ? t('common.loading') : t('voice.stopVoiceServer')}Also applies to: 364-364, 371-371
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/VoicePanel.tsx` at line 357, Replace the three hardcoded in-progress labels in the VoicePanel component (the expressions using isSaving, isStarting, isStopping) with localized strings via the t(...) function (e.g., t('voice.saving'), t('voice.starting'), t('voice.stopping')); update the JSX where {isSaving ? 'Saving…' : ...}, {isStarting ? 'Starting…' : ...}, and {isStopping ? 'Stopping…' : ...} to use those t keys and add corresponding entries to your translations file(s) so the labels are localized.app/src/pages/Mnemonic.tsx-257-258 (1)
257-258:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse direction-specific labels for the mode switch actions.
Both links use
mnemonic.reveal, but the actions are opposite (generate -> importvsimport -> generate), so at least one label is incorrect.Proposed fix
- {t('mnemonic.reveal')} + {t('mnemonic.alreadyHavePhrase')} ... - {t('mnemonic.reveal')} + {t('mnemonic.generateNewPhrase')}Also applies to: 328-329
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Mnemonic.tsx` around lines 257 - 258, Both mode-switch buttons currently use the same translation key t('mnemonic.reveal'), so their labels are incorrect for the opposite actions; locate both occurrences of t('mnemonic.reveal') used on the mode toggle buttons and replace them with direction-specific keys (e.g. t('mnemonic.switchToImport') for the "generate -> import" action and t('mnemonic.switchToGenerate') for the "import -> generate" action), then add those keys to the i18n resource files with appropriate strings so each button's label matches the action it performs.app/src/pages/Invites.tsx-215-215 (1)
215-215:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse an invite-specific empty-state key here.
accounts.noAccountsis unrelated to invite codes and can render confusing copy on this screen.Proposed fix
- {t('accounts.noAccounts')} + {t('invites.noInvites')}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Invites.tsx` at line 215, The empty-state is using the wrong translation key (t('accounts.noAccounts')) in Invites.tsx; replace it with an invite-specific key such as t('invites.noInvites') (or whatever the project's convention uses), then add corresponding entries to the i18n translation files for all supported locales and provide a sensible fallback/default string so the empty-state shows invite-specific copy rather than account-related copy.app/src/components/settings/SettingsHome.tsx-178-184 (1)
178-184:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd an accessible name to the language selector.
The
<select>has no explicit programmatic label, which can make it ambiguous for assistive technologies.Proposed fix
<select + aria-label={t('settings.language')} value={currentLocale} onChange={e => dispatch(setLocale(e.target.value as Locale))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/SettingsHome.tsx` around lines 178 - 184, The language <select> in SettingsHome (bound to currentLocale and dispatching setLocale) lacks an accessible name; add one by either wrapping it with a <label> associated via htmlFor/id or adding an aria-label/aria-labelledby that conveys "Language" (or localized equivalent) so screen readers can identify the control; ensure the id on the <select> matches the label's htmlFor if using a label, or use a clear aria-label string if not adding a visible label.app/src/components/settings/panels/MessagingPanel.tsx-77-78 (1)
77-78:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the full “active route” phrase instead of hardcoding
via.Line 77 mixes translated and hardcoded text, which limits proper localization in non-English languages.
💡 Suggested change
- return authMode ? `${channel} via ${authMode}` : t('channels.noActiveRoute'); + return authMode + ? t('channels.activeRouteValue') + .replace('{channel}', channel) + .replace('{mode}', authMode) + : t('channels.noActiveRoute');Add
channels.activeRouteValueto locale dictionaries.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/MessagingPanel.tsx` around lines 77 - 78, The code currently concatenates channel and authMode with a hardcoded "via" which breaks localization; change the return in the memo that uses channel, authMode and t to call a single translation key (e.g. 'channels.activeRouteValue') and pass channel and authMode as interpolation params to t, and add that key (with appropriate placeholders) to all locale dictionaries so translators can reorder or translate the full phrase.app/src/pages/Welcome.tsx-84-85 (1)
84-85:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConnection failure error is still partially hardcoded in English.
Line 85 builds
Connection failed: ...outside i18n. This should be a translated template too.💡 Suggested change
- const message = err instanceof Error ? err.message : t('misc.serviceUnavailable'); - setRpcUrlError(`Connection failed: ${message}`); + const message = err instanceof Error ? err.message : t('misc.serviceUnavailable'); + setRpcUrlError(t('welcome.connectionFailed').replace('{message}', message));Add
welcome.connectionFailedin locale dictionaries.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Welcome.tsx` around lines 84 - 85, The error string is partially hardcoded; replace the English "Connection failed: ..." with a translated template using the i18n function and interpolation: add a new key (e.g. welcome.connectionFailed) to the locale dictionaries and then call t('welcome.connectionFailed', { message }) when calling setRpcUrlError in the block that computes message (where message is derived from err instanceof Error ? err.message : t('misc.serviceUnavailable')); keep using setRpcUrlError and t to ensure all text is localized.app/src/components/settings/panels/AgentChatPanel.tsx-87-91 (1)
87-91:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize the overrides description paragraph.
This helper text is still hardcoded English, so the panel remains partially untranslated in zh-CN.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/AgentChatPanel.tsx` around lines 87 - 91, The paragraph under AgentChatPanel that explains overrides is hardcoded in English; update it to use the i18n translation function (e.g., replace the static string in the JSX inside AgentChatPanel with t('chat.overrides_description') or a similar key) and add the corresponding translation key/value to the locale files (e.g., en and zh-CN) so the text is localized for all languages.app/src/components/intelligence/MemoryGraph.tsx-349-365 (1)
349-365:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLocalize remaining hover-tooltip fragments.
The hover details still include hardcoded English (
canonical id, fallbackchunk), so the zh-CN flow is partially untranslated.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/MemoryGraph.tsx` around lines 349 - 365, The hover tooltip contains hardcoded English strings in the MemoryGraph component: replace the literal "canonical id" and the fallback 'chunk' with localized strings via the t() translator; specifically update the contact branch where it shows "canonical id {hovered.id.slice(0,12)}…" to use something like t('graph.canonicalId', { id: hovered.id.slice(0,12) }) and replace the fallback label used in the default branch (currently 'chunk') with t('graph.chunk') so all hover fragments are localized consistently.app/src/pages/Skills.tsx-129-141 (1)
129-141:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTranslate status labels used in tiles and aria text.
statusLabelis still sourced from English-only helpers/literals (includingStatus unavailable), so visible status text andaria-labeloutput remain untranslated.Also applies to: 195-200
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Skills.tsx` around lines 129 - 141, statusLabel and the literal "Status unavailable" are not localized; update the logic that sets statusLabel (and the similar block around lines 195-200) to use the translation function t instead of plain English strings and ensure composioStatusLabel's output is localized before use. Specifically, when hasComposioError is true replace 'Status unavailable' with t('skills.statusUnavailable') (or add a new key) and when using composioStatusLabel(connection) either change that helper to return translation keys or map its returned status into t(...) before assigning statusLabel; do the same for any aria-labels that currently use statusLabel so all visible and aria text are translated.app/src/components/settings/panels/AgentChatPanel.tsx-147-147 (1)
147-147:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTranslate the sending-state button label.
Sending…is still hardcoded and should uset(...)like the idle label.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/AgentChatPanel.tsx` at line 147, The button label uses a hardcoded "Sending…" string; update the JSX in AgentChatPanel (where the conditional renders {sending ? 'Sending…' : t('chat.sendMessage')}) to use the translation function instead (e.g., t('chat.sending')) so the sending state is localized; ensure the key you use matches your i18n keys and that useTranslation/t is available in the component.app/src/pages/Skills.tsx-958-960 (1)
958-960:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse a single interpolated translation key for uninstall success copy.
"${result.name}" ${t('common.success')}is locale-fragile. Please switch to one key with interpolation (e.g.,{name}) so grammar/order can vary by language.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Skills.tsx` around lines 958 - 960, Replace the concatenated, locale-fragile message (`"${result.name}" ${t('common.success')}`) with a single interpolated translation key and pass result.name as the interpolation value (e.g., call t('skills.disconnectSuccess', { name: result.name }) or similar) in the object assigned to message; also add/update the corresponding translation entry (e.g., "disconnectSuccess": "{name} was disconnected" or locale-appropriate variants) so grammar and word order can vary per language.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c618aa8c-9755-4d0b-a75b-314da468874e
📒 Files selected for processing (89)
app/src/App.tsxapp/src/components/BootCheckGate/BootCheckGate.tsxapp/src/components/BottomTabBar.tsxapp/src/components/channels/ChannelSetupModal.tsxapp/src/components/chat/TokenUsagePill.tsxapp/src/components/intelligence/ActionableCard.tsxapp/src/components/intelligence/BackendChooser.tsxapp/src/components/intelligence/ConfirmationModal.tsxapp/src/components/intelligence/IntelligenceCallsTab.tsxapp/src/components/intelligence/IntelligenceDreamsTab.tsxapp/src/components/intelligence/IntelligenceMemoryTab.tsxapp/src/components/intelligence/IntelligenceSubconsciousTab.tsxapp/src/components/intelligence/MemoryEmptyPlaceholder.tsxapp/src/components/intelligence/MemoryGraph.tsxapp/src/components/intelligence/MemoryHeatmap.tsxapp/src/components/intelligence/MemoryInsights.tsxapp/src/components/intelligence/MemoryNavigator.tsxapp/src/components/intelligence/MemoryResultList.tsxapp/src/components/intelligence/MemorySources.tsxapp/src/components/intelligence/MemoryStatsBar.tsxapp/src/components/intelligence/MemorySyncConnections.tsxapp/src/components/intelligence/MemoryWorkspace.tsxapp/src/components/intelligence/ModelAssignment.tsxapp/src/components/intelligence/ModelCatalog.tsxapp/src/components/intelligence/SubconsciousReflectionCards.tsxapp/src/components/intelligence/WhatsAppMemorySection.tsxapp/src/components/settings/SettingsHome.tsxapp/src/components/settings/components/SettingsHeader.tsxapp/src/components/settings/components/SettingsMenuItem.tsxapp/src/components/settings/panels/AIPanel.tsxapp/src/components/settings/panels/AboutPanel.tsxapp/src/components/settings/panels/AgentChatPanel.tsxapp/src/components/settings/panels/AutocompleteDebugPanel.tsxapp/src/components/settings/panels/AutocompletePanel.tsxapp/src/components/settings/panels/BackendProviderPanel.tsxapp/src/components/settings/panels/BillingPanel.tsxapp/src/components/settings/panels/ComposioTriagePanel.tsxapp/src/components/settings/panels/ConnectionsPanel.tsxapp/src/components/settings/panels/CronJobsPanel.tsxapp/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/LocalModelDebugPanel.tsxapp/src/components/settings/panels/LocalModelPanel.tsxapp/src/components/settings/panels/MemoryDataPanel.tsxapp/src/components/settings/panels/MemoryDebugPanel.tsxapp/src/components/settings/panels/MessagingPanel.tsxapp/src/components/settings/panels/NotificationRoutingPanel.tsxapp/src/components/settings/panels/NotificationsPanel.tsxapp/src/components/settings/panels/PrivacyPanel.tsxapp/src/components/settings/panels/RecoveryPhrasePanel.tsxapp/src/components/settings/panels/ScreenAwarenessDebugPanel.tsxapp/src/components/settings/panels/ScreenIntelligencePanel.tsxapp/src/components/settings/panels/TeamInvitesPanel.tsxapp/src/components/settings/panels/TeamManagementPanel.tsxapp/src/components/settings/panels/TeamMembersPanel.tsxapp/src/components/settings/panels/TeamPanel.tsxapp/src/components/settings/panels/ToolsPanel.tsxapp/src/components/settings/panels/VoiceDebugPanel.tsxapp/src/components/settings/panels/VoicePanel.tsxapp/src/components/settings/panels/WebhooksDebugPanel.tsxapp/src/features/human/HumanPage.tsxapp/src/features/human/MicCloudComposer.tsxapp/src/features/privacy/WhatLeavesMyComputerSheet.tsxapp/src/lib/i18n/I18nContext.tsxapp/src/lib/i18n/en.tsapp/src/lib/i18n/types.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Accounts.tsxapp/src/pages/Channels.tsxapp/src/pages/Conversations.tsxapp/src/pages/Home.tsxapp/src/pages/Intelligence.tsxapp/src/pages/Invites.tsxapp/src/pages/Mnemonic.tsxapp/src/pages/Notifications.tsxapp/src/pages/Rewards.tsxapp/src/pages/Skills.tsxapp/src/pages/Webhooks.tsxapp/src/pages/Welcome.tsxapp/src/pages/conversations/components/WorkerThreadRefCard.tsxapp/src/pages/onboarding/components/BetaBanner.tsxapp/src/pages/onboarding/components/OnboardingNextButton.tsxapp/src/pages/onboarding/pages/ChatProviderPage.tsxapp/src/pages/onboarding/steps/ContextGatheringStep.tsxapp/src/pages/onboarding/steps/LocalAIStep.tsxapp/src/pages/onboarding/steps/ReferralApplyStep.tsxapp/src/pages/onboarding/steps/SkillsStep.tsxapp/src/pages/onboarding/steps/WelcomeStep.tsxapp/src/store/index.tsapp/src/store/localeSlice.ts
graycyrus
left a comment
There was a problem hiding this comment.
Nice i18n foundation — useT() + Context is clean, fallback chain is solid, zero new deps. One blocker, three major, and a few minor catches. Findings inline.
|
Amazing thanks for the contribution @LuoYe17 merging this shortly. |
…atterns - Fix t() calls using incorrect params-object pattern to use .replace() chain - Move helper constants (CATEGORY_LABEL, DAY_LABELS, KIND_LABEL, etc.) to component-scoped functions accepting t - Localize hardcoded English in Notifications, AboutPanel, Welcome, MessagingPanel, LocalModelPanel, ComposioTriagePanel, VoicePanel, AgentChatPanel, PrivacyPanel, MemoryHeatmap, MemoryGraph, SubconsciousReflectionCards, MicCloudComposer, LocalAIStep, SkillsStep, Intelligence, Skills, and related components - Change LocalAIStep consent button from noun phrase to action verb (onboarding.enableLocalAI) - Add aria-label to SettingsHome language selector - Add missing type="button" to SettingsMenuItem - Add ~65 new translation keys to en.ts and zh-CN.ts - Run prettier formatting on changed files
The setError at persistor.purge() catch block was still hardcoded English. Add clearData.failedPersist key to both en and zh-CN translation files.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/src/pages/Intelligence.tsx (1)
108-120: ⚡ Quick winConsider extracting status mapping for readability.
The nested ternary chain works correctly but could be more maintainable as a mapping object or helper function. This would make it easier to add new status values in the future.
♻️ Refactor option: use a status mapping object
+const getSystemStatusLabel = (systemStatus: string, isRunning: boolean, t: (key: string) => string) => { + if (isRunning) return t('common.loading'); + + const statusMap: Record<string, string> = { + ready: t('common.success'), + loading: t('common.loading'), + disconnected: t('welcome.connecting'), + initializing: t('welcome.connecting'), + error: t('common.error'), + }; + + return statusMap[systemStatus] ?? t('misc.rehydrating'); +}; + -const systemStatusLabel = isRunning - ? t('common.loading') - : systemStatus === 'ready' - ? t('common.success') - : systemStatus === 'loading' - ? t('common.loading') - : systemStatus === 'disconnected' - ? t('welcome.connecting') - : systemStatus === 'initializing' - ? t('welcome.connecting') - : systemStatus === 'error' - ? t('common.error') - : t('misc.rehydrating'); +const systemStatusLabel = getSystemStatusLabel(systemStatus, isRunning, t);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/Intelligence.tsx` around lines 108 - 120, The nested ternary used to compute systemStatusLabel (using isRunning and systemStatus) is hard to read; replace it with a clearer mapping or helper function: create a status->translation-key map (e.g., const STATUS_LABELS = { ready: 'common.success', loading: 'common.loading', disconnected: 'welcome.connecting', initializing: 'welcome.connecting', error: 'common.error', /* ... */ }) and a small getter (e.g., getStatusLabel(status): string) that returns t(STATUS_LABELS[status] || 'misc.rehydrating'); keep the isRunning check as the highest precedence (if isRunning return t('common.loading') else return getStatusLabel(systemStatus)); update the reference to systemStatusLabel in Intelligence.tsx accordingly for improved readability and easier extension.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/intelligence/SubconsciousReflectionCards.tsx`:
- Around line 46-61: The kindLabel function can return undefined for unknown
ReflectionKind values; update kindLabel (the switch in function kindLabel) to
include a runtime fallback (add a default case or final return) that returns a
sensible string (e.g., t('reflections.kind.unknown') or the raw kind string) so
the component never receives undefined; ensure the fallback is used when an
unexpected kind arrives from the backend.
- Around line 76-81: The relative-time buckets use Math.round which can push
values to the next bucket prematurely; change the seconds calculation and all
subsequent conversions to use Math.floor instead of Math.round so diffSec =
Math.max(0, Math.floor((nowMs - tsMs) / 1000)) and when returning
minutes/hours/days use Math.floor(diffSec / 60), Math.floor(diffSec / 3600), and
Math.floor(diffSec / 86400) respectively (update the expressions around diffSec
and the t(...) calls in SubconsciousReflectionCards.tsx).
---
Nitpick comments:
In `@app/src/pages/Intelligence.tsx`:
- Around line 108-120: The nested ternary used to compute systemStatusLabel
(using isRunning and systemStatus) is hard to read; replace it with a clearer
mapping or helper function: create a status->translation-key map (e.g., const
STATUS_LABELS = { ready: 'common.success', loading: 'common.loading',
disconnected: 'welcome.connecting', initializing: 'welcome.connecting', error:
'common.error', /* ... */ }) and a small getter (e.g., getStatusLabel(status):
string) that returns t(STATUS_LABELS[status] || 'misc.rehydrating'); keep the
isRunning check as the highest precedence (if isRunning return
t('common.loading') else return getStatusLabel(systemStatus)); update the
reference to systemStatusLabel in Intelligence.tsx accordingly for improved
readability and easier extension.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6609b41b-47e3-4ed8-9f5a-8593fe04c0a8
📒 Files selected for processing (28)
app/src/components/BootCheckGate/BootCheckGate.tsxapp/src/components/intelligence/MemoryGraph.tsxapp/src/components/intelligence/MemoryHeatmap.tsxapp/src/components/intelligence/MemorySyncConnections.tsxapp/src/components/intelligence/SubconsciousReflectionCards.tsxapp/src/components/intelligence/WhatsAppMemorySection.tsxapp/src/components/settings/SettingsHome.tsxapp/src/components/settings/components/SettingsMenuItem.tsxapp/src/components/settings/panels/AboutPanel.tsxapp/src/components/settings/panels/AgentChatPanel.tsxapp/src/components/settings/panels/ComposioTriagePanel.tsxapp/src/components/settings/panels/LocalModelPanel.tsxapp/src/components/settings/panels/MessagingPanel.tsxapp/src/components/settings/panels/PrivacyPanel.tsxapp/src/components/settings/panels/VoicePanel.tsxapp/src/features/human/MicCloudComposer.tsxapp/src/lib/i18n/I18nContext.tsxapp/src/lib/i18n/en.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Intelligence.tsxapp/src/pages/Invites.tsxapp/src/pages/Mnemonic.tsxapp/src/pages/Notifications.tsxapp/src/pages/Skills.tsxapp/src/pages/Welcome.tsxapp/src/pages/onboarding/steps/LocalAIStep.tsxapp/src/pages/onboarding/steps/SkillsStep.tsxapp/src/services/meetCallService.ts
✅ Files skipped from review due to trivial changes (3)
- app/src/services/meetCallService.ts
- app/src/lib/i18n/zh-CN.ts
- app/src/pages/onboarding/steps/LocalAIStep.tsx
🚧 Files skipped from review as they are similar to previous changes (20)
- app/src/components/settings/panels/LocalModelPanel.tsx
- app/src/components/intelligence/WhatsAppMemorySection.tsx
- app/src/lib/i18n/I18nContext.tsx
- app/src/pages/Notifications.tsx
- app/src/components/settings/panels/AgentChatPanel.tsx
- app/src/components/intelligence/MemoryGraph.tsx
- app/src/features/human/MicCloudComposer.tsx
- app/src/components/settings/components/SettingsMenuItem.tsx
- app/src/lib/i18n/en.ts
- app/src/pages/onboarding/steps/SkillsStep.tsx
- app/src/pages/Mnemonic.tsx
- app/src/components/settings/panels/ComposioTriagePanel.tsx
- app/src/components/settings/panels/AboutPanel.tsx
- app/src/components/BootCheckGate/BootCheckGate.tsx
- app/src/pages/Skills.tsx
- app/src/components/settings/panels/PrivacyPanel.tsx
- app/src/components/settings/SettingsHome.tsx
- app/src/components/intelligence/MemorySyncConnections.tsx
- app/src/components/settings/panels/VoicePanel.tsx
- app/src/pages/Invites.tsx
Tests render components without I18nProvider, so useT() falls back to the default context value. The default t() function previously returned the raw key string (e.g. "mnemonic.title"), causing 241 test failures. Now it looks up translations.en so components get proper English strings even without a Provider wrapper.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/src/lib/i18n/I18nContext.tsx (1)
10-13: ⚡ Quick winExtract the repeated context type to an interface.
The type
{ t: (key: string) => string; locale: Locale }is defined inline on both line 10 and line 31. As per coding guidelines, prefer interface for defining object shapes in TypeScript. Extracting this to a shared interface improves maintainability and reduces duplication.As per coding guidelines: "Prefer interface for defining object shapes in TypeScript"♻️ Proposed refactor
+interface I18nContextValue { + t: (key: string) => string; + locale: Locale; +} + -const I18nContext = createContext<{ t: (key: string) => string; locale: Locale }>({ +const I18nContext = createContext<I18nContextValue>({ t: (key: string) => translations.en[key] ?? key, locale: 'en', });-export function useT(): { t: (key: string) => string; locale: Locale } { +export function useT(): I18nContextValue { return useContext(I18nContext); }Also applies to: 31-33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/lib/i18n/I18nContext.tsx` around lines 10 - 13, Extract the inline context type into a named interface (e.g., I18nContextType) and replace the duplicated inline type usages with that interface: change the createContext call for I18nContext to use I18nContextType instead of the inline `{ t: (key: string) => string; locale: Locale }`, and update any other occurrences (such as the type used around the consumer/hook defined later in this file) to reference I18nContextType so the shape is defined once and reused.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/src/lib/i18n/I18nContext.tsx`:
- Around line 10-13: Extract the inline context type into a named interface
(e.g., I18nContextType) and replace the duplicated inline type usages with that
interface: change the createContext call for I18nContext to use I18nContextType
instead of the inline `{ t: (key: string) => string; locale: Locale }`, and
update any other occurrences (such as the type used around the consumer/hook
defined later in this file) to reference I18nContextType so the shape is defined
once and reused.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6d1ff78a-0d99-476b-ba82-7565684bbdbe
📒 Files selected for processing (5)
app/src/components/settings/SettingsHome.tsxapp/src/lib/i18n/I18nContext.tsxapp/src/lib/i18n/en.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/onboarding/steps/SkillsStep.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- app/src/pages/onboarding/steps/SkillsStep.tsx
- app/src/lib/i18n/zh-CN.ts
- app/src/lib/i18n/en.ts
- app/src/components/settings/SettingsHome.tsx
- Webhooks.tsx: fix archive/today labels using wrong key (common.refresh → webhooks.*) - SubconsciousReflectionCards: fix kindLabel missing default fallback (undefined bug) - SubconsciousReflectionCards: fix formatRelativeTime using Math.round (premature bucket) - SubconsciousReflectionCards: fix formatRelativeTime passing 2nd arg to t() (silently ignored) - Add missing keys: memory.analyzeNow, webhooks.archiveDirectory, webhooks.todayFile
The en module may be wrapped as { default: {...} } in test runners
using CJS/ESM interop (e.g. Vitest with certain transforms). Detect
this and unwrap, falling back to the translations record.
Previous approach computed enFallback at module init, but in Vitest the en module may not be fully resolved at init time (returns empty object). Move resolution to call time so tests running without I18nProvider get proper English translations via resolveEn().
Summary
Add simplified Chinese (简体中文) localization support to OpenHuman.
useT()hook — zero new dependencieslocaleSlicewith browser language auto-detection and localStorage persistenceArchitecture
app/src/lib/i18n/types.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/zh-CN.tsapp/src/lib/i18n/I18nContext.tsxapp/src/store/localeSlice.tsComponents use
const { t } = useT()thent('key.path')for all user-visible strings.Test plan
cargo check -p openhumanpasses)Notes
set-state-in-effectwarnings (not from this PR)Summary by CodeRabbit
New Features
Chores