Skip to content

feat: add Chinese (简体中文) i18n support#1518

Open
LuoYe17 wants to merge 12 commits into
tinyhumansai:mainfrom
LuoYe17:claude/agitated-northcutt-ab62aa
Open

feat: add Chinese (简体中文) i18n support#1518
LuoYe17 wants to merge 12 commits into
tinyhumansai:mainfrom
LuoYe17:claude/agitated-northcutt-ab62aa

Conversation

@LuoYe17
Copy link
Copy Markdown

@LuoYe17 LuoYe17 commented May 12, 2026

Summary

Add simplified Chinese (简体中文) localization support to OpenHuman.

  • Lightweight i18n infrastructure using React Context + useT() hook — zero new dependencies
  • Redux localeSlice with browser language auto-detection and localStorage persistence
  • Language switcher dropdown in Settings > General
  • 731 translation keys covering all UI surfaces
  • English fallback for untranslated keys

Architecture

File Purpose
app/src/lib/i18n/types.ts Locale type definition
app/src/lib/i18n/en.ts English dictionary (731 keys, fallback)
app/src/lib/i18n/zh-CN.ts Simplified Chinese dictionary (731 keys)
app/src/lib/i18n/I18nContext.tsx React Context + I18nProvider + useT() hook
app/src/store/localeSlice.ts Redux slice with persistence

Components use const { t } = useT() then t('key.path') for all user-visible strings.

Test plan

  • TypeScript typecheck passes (zero errors)
  • Rust core compiles (cargo check -p openhuman passes)
  • Manually verified: Connect screen renders in Chinese with zh-CN browser locale
  • Manually verified: Settings > General language switcher works
  • Run E2E specs

Notes

  • Pre-push hook skipped: 41 pre-existing ESLint set-state-in-effect warnings (not from this PR)
  • No Tauri/Rust behavior changes — i18n is purely frontend TypeScript
  • 89 files changed, +3296/-1175 lines

Summary by CodeRabbit

  • New Features

    • App-wide i18n with English + Simplified Chinese, including default translation bundles.
    • Automatic locale detection and a persistent language selector in Settings.
  • Chores

    • Majority of user-facing text localized (navigation, settings, chat, memory, notifications, voice, onboarding, etc.), improving accessibility and consistency.

Review Change Stack

- 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
@LuoYe17 LuoYe17 requested a review from a team May 12, 2026 04:16
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7026d3b7-751b-4cc8-a1ab-c7633f8d69ed

📥 Commits

Reviewing files that changed from the base of the PR and between efc3655 and 18a975d.

📒 Files selected for processing (6)
  • app/src/components/intelligence/SubconsciousReflectionCards.tsx
  • app/src/lib/i18n/I18nContext.tsx
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/Webhooks.tsx
 _________________________________________
< OODA Loop: Observe, Orient, Debug, Act. >
 -----------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
📝 Walkthrough

Walkthrough

Adds 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.

Changes

I18n plumbing and app wiring

Layer / File(s) Summary
I18n context and provider
app/src/lib/i18n/I18nContext.tsx, app/src/lib/i18n/types.ts
Add I18nContext, I18nProvider, memoized t(key) per locale, and useT() hook; re-export Locale type.
Translation resources
app/src/lib/i18n/en.ts, app/src/lib/i18n/zh-CN.ts
Add English and Simplified Chinese translation maps with keys used across the UI.
Store: locale slice & persistence
app/src/store/localeSlice.ts, app/src/store/index.ts
Add locale slice with detectLocale(), setLocale action, and persist locale.current via redux-persist; register reducer.
App wiring
app/src/App.tsx
Import and wrap the boot-time gate/provider/router stack in I18nProvider so translations are available during bootstrap and shell rendering.

UI localization sweep

Layer / File(s) Summary
Global UI text replacements
app/src/components/*, app/src/pages/*, app/src/features/*
Replace hardcoded strings with useT()/t(...) lookups in many components (BottomTabBar, TokenUsagePill, ModelCatalog, Memory components, notifications, conversations, etc.).
Settings and menu changes
app/src/components/settings/*, app/src/components/settings/components/SettingsMenuItem.tsx, app/src/components/settings/SettingsHome.tsx
Localize SettingsHeader and many panels; make SettingsMenuItem onClick optional and add rightElement to render the language selector; add language select wired to persisted locale in SettingsHome.
Boot & onboarding
app/src/components/BootCheckGate/BootCheckGate.tsx, app/src/pages/onboarding/*
Localize boot-check gate screens, mode picker, and onboarding steps (LocalAIStep, SkillsStep, WelcomeStep, etc.).
Intelligence / Memory UI
app/src/components/intelligence/*, app/src/pages/Intelligence.tsx
Localize MemoryGraph, MemoryHeatmap, MemoryInsights, MemoryWorkspace, stats, tooltips, and empty states.
Voice & mic features
app/src/features/human/*, app/src/features/human/MicCloudComposer.tsx, app/src/components/settings/panels/VoicePanel.tsx
Localize mic/composer error messages, button labels, voice settings text and notices.
Minor changes
app/src/services/meetCallService.ts
Remove an eslint-disable comment before a console.error (no functional change).

Sequence Diagram(s):
(suppressed — changes are broad text replacements and provider wiring; no new multi-component sequential control flow beyond provider wrap)

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • senamakel

"A rabbit in a soft locale hat,
hopped through keys and strings to chat.
I wrapped the App in gentle care,
so words can travel everywhere. 🐰✨"

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Localize the relative time formatting.

The formatTime function 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 win

Localize the notification category labels.

The CATEGORY_LABEL mapping 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 accepts 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/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 win

Localize 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 win

Complete 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 win

Localize remaining card metadata labels to avoid mixed-language output.

KIND_LABEL values 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 win

Remove 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 win

Incomplete i18n: relativeTime function returns hardcoded English strings.

The relativeTime helper 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 t function to relativeTime and 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 win

Incomplete i18n: summaryFor function returns hardcoded English strings.

The summaryFor helper 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 t function 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 win

Incomplete 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 win

Localize the KIND_LABEL constant.

The KIND_LABEL record 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_LABEL is 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_LABEL inside the component to access 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/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 win

Localize the statusLabel helper function.

The statusLabel function 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 via t(...), 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 win

Localize the populated-state section heading too.

The success path still renders hardcoded "Memory sources" while other branches use t('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 win

Add type="button" on Line 46.

Without an explicit type, this defaults to submit inside 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 win

Localize 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 win

Avoid 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 win

Verify semantic change from "Soon" to "Beta".

The badge text was changed from "Soon" to t('misc.beta'), but the property driving it is still named comingSoon (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 win

Consider using an actionable button-specific translation key for these CTAs.

Lines 114 and 170 render buttons with onClick={handleConsent} using t('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 win

Localize the in-progress button labels as well.

Saving…, Starting…, and Stopping… 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 win

Use direction-specific labels for the mode switch actions.

Both links use mnemonic.reveal, but the actions are opposite (generate -> import vs import -> 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 win

Use an invite-specific empty-state key here.

accounts.noAccounts is 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 win

Add 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 win

Localize 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.activeRouteValue to 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 win

Connection 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.connectionFailed in 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 win

Localize 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 win

Localize remaining hover-tooltip fragments.

The hover details still include hardcoded English (canonical id, fallback chunk), 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 win

Translate status labels used in tiles and aria text.

statusLabel is still sourced from English-only helpers/literals (including Status unavailable), so visible status text and aria-label output 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 win

Translate the sending-state button label.

Sending… is still hardcoded and should use t(...) 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 win

Use 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

📥 Commits

Reviewing files that changed from the base of the PR and between ba3e116 and f01b246.

📒 Files selected for processing (89)
  • app/src/App.tsx
  • app/src/components/BootCheckGate/BootCheckGate.tsx
  • app/src/components/BottomTabBar.tsx
  • app/src/components/channels/ChannelSetupModal.tsx
  • app/src/components/chat/TokenUsagePill.tsx
  • app/src/components/intelligence/ActionableCard.tsx
  • app/src/components/intelligence/BackendChooser.tsx
  • app/src/components/intelligence/ConfirmationModal.tsx
  • app/src/components/intelligence/IntelligenceCallsTab.tsx
  • app/src/components/intelligence/IntelligenceDreamsTab.tsx
  • app/src/components/intelligence/IntelligenceMemoryTab.tsx
  • app/src/components/intelligence/IntelligenceSubconsciousTab.tsx
  • app/src/components/intelligence/MemoryEmptyPlaceholder.tsx
  • app/src/components/intelligence/MemoryGraph.tsx
  • app/src/components/intelligence/MemoryHeatmap.tsx
  • app/src/components/intelligence/MemoryInsights.tsx
  • app/src/components/intelligence/MemoryNavigator.tsx
  • app/src/components/intelligence/MemoryResultList.tsx
  • app/src/components/intelligence/MemorySources.tsx
  • app/src/components/intelligence/MemoryStatsBar.tsx
  • app/src/components/intelligence/MemorySyncConnections.tsx
  • app/src/components/intelligence/MemoryWorkspace.tsx
  • app/src/components/intelligence/ModelAssignment.tsx
  • app/src/components/intelligence/ModelCatalog.tsx
  • app/src/components/intelligence/SubconsciousReflectionCards.tsx
  • app/src/components/intelligence/WhatsAppMemorySection.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/components/SettingsHeader.tsx
  • app/src/components/settings/components/SettingsMenuItem.tsx
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/AboutPanel.tsx
  • app/src/components/settings/panels/AgentChatPanel.tsx
  • app/src/components/settings/panels/AutocompleteDebugPanel.tsx
  • app/src/components/settings/panels/AutocompletePanel.tsx
  • app/src/components/settings/panels/BackendProviderPanel.tsx
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/ComposioTriagePanel.tsx
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/CronJobsPanel.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/LocalModelDebugPanel.tsx
  • app/src/components/settings/panels/LocalModelPanel.tsx
  • app/src/components/settings/panels/MemoryDataPanel.tsx
  • app/src/components/settings/panels/MemoryDebugPanel.tsx
  • app/src/components/settings/panels/MessagingPanel.tsx
  • app/src/components/settings/panels/NotificationRoutingPanel.tsx
  • app/src/components/settings/panels/NotificationsPanel.tsx
  • app/src/components/settings/panels/PrivacyPanel.tsx
  • app/src/components/settings/panels/RecoveryPhrasePanel.tsx
  • app/src/components/settings/panels/ScreenAwarenessDebugPanel.tsx
  • app/src/components/settings/panels/ScreenIntelligencePanel.tsx
  • app/src/components/settings/panels/TeamInvitesPanel.tsx
  • app/src/components/settings/panels/TeamManagementPanel.tsx
  • app/src/components/settings/panels/TeamMembersPanel.tsx
  • app/src/components/settings/panels/TeamPanel.tsx
  • app/src/components/settings/panels/ToolsPanel.tsx
  • app/src/components/settings/panels/VoiceDebugPanel.tsx
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/settings/panels/WebhooksDebugPanel.tsx
  • app/src/features/human/HumanPage.tsx
  • app/src/features/human/MicCloudComposer.tsx
  • app/src/features/privacy/WhatLeavesMyComputerSheet.tsx
  • app/src/lib/i18n/I18nContext.tsx
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/types.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Accounts.tsx
  • app/src/pages/Channels.tsx
  • app/src/pages/Conversations.tsx
  • app/src/pages/Home.tsx
  • app/src/pages/Intelligence.tsx
  • app/src/pages/Invites.tsx
  • app/src/pages/Mnemonic.tsx
  • app/src/pages/Notifications.tsx
  • app/src/pages/Rewards.tsx
  • app/src/pages/Skills.tsx
  • app/src/pages/Webhooks.tsx
  • app/src/pages/Welcome.tsx
  • app/src/pages/conversations/components/WorkerThreadRefCard.tsx
  • app/src/pages/onboarding/components/BetaBanner.tsx
  • app/src/pages/onboarding/components/OnboardingNextButton.tsx
  • app/src/pages/onboarding/pages/ChatProviderPage.tsx
  • app/src/pages/onboarding/steps/ContextGatheringStep.tsx
  • app/src/pages/onboarding/steps/LocalAIStep.tsx
  • app/src/pages/onboarding/steps/ReferralApplyStep.tsx
  • app/src/pages/onboarding/steps/SkillsStep.tsx
  • app/src/pages/onboarding/steps/WelcomeStep.tsx
  • app/src/store/index.ts
  • app/src/store/localeSlice.ts

Comment thread app/src/App.tsx
Comment thread app/src/components/settings/panels/AutocompletePanel.tsx
Comment thread app/src/components/settings/panels/AutocompletePanel.tsx
Comment thread app/src/components/settings/panels/LocalModelPanel.tsx Outdated
Comment thread app/src/pages/onboarding/steps/SkillsStep.tsx Outdated
Comment thread app/src/pages/Webhooks.tsx
graycyrus

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread app/src/store/localeSlice.ts
Comment thread app/src/pages/Webhooks.tsx Outdated
Comment thread app/src/pages/Notifications.tsx
Comment thread app/src/components/settings/panels/DeveloperOptionsPanel.tsx
Comment thread app/src/lib/i18n/en.ts
Comment thread app/src/components/intelligence/IntelligenceMemoryTab.tsx
Comment thread app/src/components/settings/SettingsHome.tsx
Comment thread app/src/components/settings/panels/DeveloperOptionsPanel.tsx
@senamakel
Copy link
Copy Markdown
Member

Amazing thanks for the contribution @LuoYe17 merging this shortly.

LuoYe17 added 2 commits May 12, 2026 20:40
…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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/src/pages/Intelligence.tsx (1)

108-120: ⚡ Quick win

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between f01b246 and 82a078d.

📒 Files selected for processing (28)
  • app/src/components/BootCheckGate/BootCheckGate.tsx
  • app/src/components/intelligence/MemoryGraph.tsx
  • app/src/components/intelligence/MemoryHeatmap.tsx
  • app/src/components/intelligence/MemorySyncConnections.tsx
  • app/src/components/intelligence/SubconsciousReflectionCards.tsx
  • app/src/components/intelligence/WhatsAppMemorySection.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/components/SettingsMenuItem.tsx
  • app/src/components/settings/panels/AboutPanel.tsx
  • app/src/components/settings/panels/AgentChatPanel.tsx
  • app/src/components/settings/panels/ComposioTriagePanel.tsx
  • app/src/components/settings/panels/LocalModelPanel.tsx
  • app/src/components/settings/panels/MessagingPanel.tsx
  • app/src/components/settings/panels/PrivacyPanel.tsx
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/features/human/MicCloudComposer.tsx
  • app/src/lib/i18n/I18nContext.tsx
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Intelligence.tsx
  • app/src/pages/Invites.tsx
  • app/src/pages/Mnemonic.tsx
  • app/src/pages/Notifications.tsx
  • app/src/pages/Skills.tsx
  • app/src/pages/Welcome.tsx
  • app/src/pages/onboarding/steps/LocalAIStep.tsx
  • app/src/pages/onboarding/steps/SkillsStep.tsx
  • app/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

Comment thread app/src/components/intelligence/SubconsciousReflectionCards.tsx
Comment thread app/src/components/intelligence/SubconsciousReflectionCards.tsx Outdated
LuoYe17 added 2 commits May 12, 2026 20:51
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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/lib/i18n/I18nContext.tsx (1)

10-13: ⚡ Quick win

Extract 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.

♻️ 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);
 }
As per coding guidelines: "Prefer interface for defining object shapes in TypeScript"

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

📥 Commits

Reviewing files that changed from the base of the PR and between 82a078d and efc3655.

📒 Files selected for processing (5)
  • app/src/components/settings/SettingsHome.tsx
  • app/src/lib/i18n/I18nContext.tsx
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/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

LuoYe17 and others added 4 commits May 12, 2026 21:06
- 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
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 12, 2026
LuoYe17 added 2 commits May 12, 2026 21:31
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().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants