i18n: full UI coverage across 10 languages#1986
Conversation
Extends the Locale type and registers empty stub catalogs for the seven new languages. Each falls back to English via the existing resolveEn() path, so no UI regressions while string extraction is in flight. LanguageSelect now lists all ten supported locales (English label rendered in each locale's own script). detectLocale() recognises the new BCP-47 prefixes. I18nProvider mirrors locale + direction onto <html> so RTL (Arabic) and per-language hyphenation light up automatically when the catalogs are populated.
Migrates the one user-visible string in WhatLeavesLink to useT(). All Mascot files (BackendMascot, Defs, Ghosty, frameContext, LoadingFace, MascotCharacter, MascotIdle, MascotTalking, MascotThinking, RecordingFace, YellowMascot, MascotFrameProducer) are pure SVG/canvas rendering with no user-visible UI strings and are skipped per instructions.
Migrated user-visible strings to useT() hook in: - onboarding/steps/ContextGatheringStep.tsx (title, error state, building profile text, stage labels, CTA button) - conversations/components/TaskKanbanBoard.tsx (column labels, heading, move-card aria-labels) - conversations/components/ToolTimelineBlock.tsx (worker thread badge, turn counter) - pages/Settings.tsx (section page titles/descriptions for account, features, and AI sub-sections; extracted icon JSX to module-level constants)
Migrated 12 components across channels/ and intelligence/ to use the useT() hook. ChannelCapabilities, ChannelConfigPanel, ChannelFieldInput, ChannelStatusBadge, and Toast had no hardcoded user-visible strings to extract. All new keys are returned in the key block below for merging into en.ts and the locale files.
…webhooks Salvaged from the misc batch before its watchdog stalled. Remaining files in the misc bucket (oauth, composio, walkthrough, notifications, rewards, chat/home/daemon/ui, top-level singletons) will be re-dispatched in smaller batches.
Salvaged from the settings batch before its watchdog stalled at InferenceBudget. Remaining settings files (billing tail, local-model, cron, screen-intel, chrome) re-dispatched in smaller batches.
Migrates all 14 skill-area components to use useT() / t() for every user-visible label, heading, button, placeholder, error message, and aria-label. No changes to en.ts or any locale file.
Migrate MemoryWindowControl, InferenceBudget, and SubscriptionPlans to the useT() hook. SettingsSectionPage, SettingsMenuItem, PageBackButton, BillingPlansTab, and BillingPaymentsTab have no hardcoded UI strings (they are pure props/composition) — no changes needed there.
Migrates all user-visible strings in OAuthProviderButton, OAuthLoginSection, TriggerToggles, ComposioConnectModal, and WalkthroughTooltip to the useT hook. providerConfigs, toolkitMeta, and AppWalkthrough have no extractable strings.
Migrate user-visible text in error fallback, update prompt, persist rehydration, local-AI download snackbar, openhuman-link modal, connection badge/indicator, route loading screen, usage-limit modal, and the overlay to t() calls. Pure-visual/router/provider files are no-ops and left untouched.
Wire useT() into NotificationCenter, NotificationCard, RewardsCommunityTab, RewardsCouponSection, ReferralRewardsSection, UnsubscribeApprovalCard, HomeBanners, and ServiceBlockingGate. Button.tsx and the two thin tab wrappers (RewardsRedeemTab, RewardsReferralsTab) have no built-in text and are skipped.
Auto-merged from the cross-area extraction wave: 471 values were
recovered from the i18n commit diffs (paired removed-string ↔ added
t('key') replacements), and 341 fall back to a humanised label derived
from the key name. Template-style ${var} placeholders are normalised
to {var} so translators can reuse them positionally.
Verified: every t('key') call site in app/src now resolves to a key
in en.ts (sanity check: comm -23 keys-used keys-en == 0).
Each locale aggregates its translations from 5 chunk files under chunks/<locale>-N.ts so translation agents can produce one chunk per Write call without hitting the 32k output-token limit. Existing Indonesian and Mandarin translations are recovered from the original single-file catalogs and pre-populated into the matching chunks; gaps in those locales (and all of the seven newly-added languages) will be filled by per-chunk translation passes.
Translate all 5 chunks (~1,720 keys total) from English into neutral Latin American Spanish with a friendly consumer-app tone (tú form). Brand names, placeholders, tech tokens, and code-interpolation strings are preserved verbatim per translation rules.
Full Devanagari translation of all 5 chunks (~1700+ keys) using casual modern app tone (Hindi YouTube/Instagram style). All placeholders, brands, keyboard tokens, and technical terms preserved unchanged.
All 5 chunks translated to standard metropolitan French with friendly "tu" form (Slack/WhatsApp FR style). Brand names, placeholders, and tech tokens preserved unchanged.
Full Brazilian Portuguese translation of all 5 chunks (~900 keys) using friendly "você" tone. Brand names, placeholders, and tech tokens preserved as-is per translation rules.
Full Cyrillic translation of all 5 chunks (~700 keys). Modern consumer-app tone, informal «ты» form, matching Telegram RU register. Brand names, placeholders, tech tokens, and untranslatable template strings left as-is.
Translates all 5 en-N.ts chunks into Modern Standard Arabic (العربية الفصحى، صياغة عصرية تطبيقية). All placeholders, brand names, and tech tokens kept unchanged. Typecheck passes.
…10-languages # Conflicts: # app/src/components/OpenhumanLinkModal.tsx # app/src/components/composio/ComposioConnectModal.tsx # app/src/lib/i18n/zh-CN.ts
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughApp-wide i18n: replaces hardcoded UI strings with useT()/t(...), adds merged locale chunks, and updates I18nProvider to mirror html lang/dir. ChangesApp-wide internationalization and locale updates
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as User (rgba(33, 150, 243, 0.5))
participant UI as UI Component (rgba(76, 175, 80, 0.5))
participant I18n as I18nProvider (rgba(255, 193, 7, 0.5))
participant Doc as Document <html> (rgba(158, 158, 158, 0.5))
User->>UI: Open screen
UI->>I18n: useT() -> request t(key)
I18n-->>UI: localized string
I18n->>Doc: set lang, dir (per locale/RTL)
UI-->>User: Render translated UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
|
Resolves remaining failures from the latest CI run:
- Restore long ComposioPanel descriptions (modeDirect/modeManaged) and
intro that lost their original wording during agent extraction; tests
assert the 'not yet routed in direct mode' contract string.
- Disambiguate common.copied: rewards keeps 'Copied' (capital), MemoryChunkDetail
switches to intelligence.memoryChunk.detail.copiedHint = 'copied'
to satisfy the lowercase assertion.
- Strip placeholder from intelligence.memoryText.entityTypePrefix since
the call site concatenates ': value' itself.
- Add missing conversations.taskKanban.{todo,inProgress,blocked,done}
keys the component referenced but were never created.
- Fix settings.mascot.noCharacters (real call-site key) to match the
longer expected wording, and skills.uninstall.title = 'Uninstall' so
the rendered '${title} ${name}?' satisfies '/Uninstall weather-helper\?/'.
- Restore embeddingInfo, uninstall.description, and Composio confirm-
dialog wording to their main-branch values.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/lib/i18n/chunks/bn-5.ts`:
- Line 365: The two i18n keys 'webhooks.tunnels.enableEcho' and
'webhooks.tunnels.removeEcho' currently share the same Bengali text; update
'webhooks.tunnels.enableEcho' to a proper "enable" translation (e.g., change
from "ইকো সরান" to a phrase meaning "enable echo" such as "ইকো চালু করুন") while
keeping 'webhooks.tunnels.removeEcho' as "ইকো সরান" so the enable and remove
states render distinctly; locate these keys in bn-5.ts and replace only the
enableEcho value.
- Around line 9-23: This file contains many untranslated English UI strings
(e.g. 'settings.composio.apiKeyDesc', 'settings.composio.apiKeyLabel',
'settings.composio.apiKeyStored', 'settings.composio.confirmItem1',
'settings.composio.confirmTitle', 'settings.composio.intro', etc.); replace each
raw English value with the proper Bengali translation for the corresponding i18n
key, or if you intentionally want to fall back to English for specific keys,
remove the English text from the bn bundle and ensure the renderer uses the 'en'
fallback at runtime instead of mixing languages in this file; update all
remaining untranslated keys called out in the comment (and the rest of the
chunk) so the bn-5.ts bundle is fully Bengali or defers to en at render-time.
- Line 41: The translations for state/visibility keys are inverted: update the
Bengali strings so they match the key semantics — change
'settings.cron.jobs.paused' and 'settings.cron.jobs.inactive' from "সক্রিয়"
(active) to the correct inactive wording (e.g., "নিষ্ক্রিয়"), and swap the
show/hide error-detail labels (the keys around lines 156-157, e.g., the
show*ErrorDetails and hide*ErrorDetails keys) so "লুকান" is used for hide and
the appropriate "দেখান"/"প্রদর্শন" is used for show; locate and correct the
values for the keys 'settings.cron.jobs.paused', 'settings.cron.jobs.inactive'
and the show/hide error detail keys in bn-5.ts accordingly.
- Line 357: The product label in the translation key
'webhooks.composioHistory.title' is inconsistent (mix of "ComposeIO" and
"Composio"); update the value for 'webhooks.composioHistory.title' to use the
standardized product name (choose either "ComposeIO" or "Composio" per product
standard) so the displayed string matches the official branding across the app.
🪄 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: 17b28c9b-fdfd-4671-97b3-35026d05d237
📒 Files selected for processing (22)
app/src/components/intelligence/MemoryChunkDetail.tsxapp/src/lib/i18n/chunks/ar-4.tsapp/src/lib/i18n/chunks/ar-5.tsapp/src/lib/i18n/chunks/bn-4.tsapp/src/lib/i18n/chunks/bn-5.tsapp/src/lib/i18n/chunks/en-4.tsapp/src/lib/i18n/chunks/en-5.tsapp/src/lib/i18n/chunks/es-4.tsapp/src/lib/i18n/chunks/es-5.tsapp/src/lib/i18n/chunks/fr-4.tsapp/src/lib/i18n/chunks/fr-5.tsapp/src/lib/i18n/chunks/hi-4.tsapp/src/lib/i18n/chunks/hi-5.tsapp/src/lib/i18n/chunks/id-4.tsapp/src/lib/i18n/chunks/id-5.tsapp/src/lib/i18n/chunks/pt-4.tsapp/src/lib/i18n/chunks/pt-5.tsapp/src/lib/i18n/chunks/ru-4.tsapp/src/lib/i18n/chunks/ru-5.tsapp/src/lib/i18n/chunks/zh-CN-4.tsapp/src/lib/i18n/chunks/zh-CN-5.tsapp/src/lib/i18n/en.ts
✅ Files skipped from review due to trivial changes (6)
- app/src/lib/i18n/chunks/ar-4.ts
- app/src/lib/i18n/chunks/es-4.ts
- app/src/lib/i18n/chunks/en-5.ts
- app/src/lib/i18n/chunks/fr-5.ts
- app/src/lib/i18n/chunks/ar-5.ts
- app/src/lib/i18n/chunks/fr-4.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- app/src/components/intelligence/MemoryChunkDetail.tsx
- app/src/lib/i18n/chunks/bn-4.ts
- app/src/lib/i18n/chunks/en-4.ts
- app/src/lib/i18n/chunks/es-5.ts
- Replace Composio direct-mode confirm dialog wording with the literal
main-branch copy that tests assert ('triggers (real-time webhooks)
don't fire in Direct mode yet', 'You'll need:' list with the three
specific bullets, etc).
- Stop double-prefixing the uninstall RPC error: setError(msg) only —
the inline error block already renders 'Could not uninstall' as a
heading, so prepending it again produced two matches and broke
getByText(/Could not uninstall/).
- skills.uninstall.uninstallBtn = 'Uninstall' (was 'Uninstalling…'),
so the confirm button now matches /^Uninstall$/.
- toolTimeline.turn was 'turn ${subagent.childIteration}/...' (raw template
literal captured by auto-extractor); the call site appends the counter
via string concat, so the value is just 'turn'.
- meetingBots.couldNotStartTitle was duplicated from busyTitle; restored
to 'Could not start OpenHuman' so the non-capacity error toast title
matches the test's /not start/i assertion.
- All four memoryWindow tiers had .badge='Badge' and .hint='Hint'
fallback values; restored the descriptive copy (Cheapest / Recommended
/ More context / Highest cost + per-tier explanations).
…10-languages # Conflicts: # app/src/components/settings/panels/local-model/DeviceCapabilitySection.tsx # app/src/components/settings/panels/local-model/ModelDownloadSection.tsx # app/src/components/settings/panels/local-model/ModelStatusSection.tsx
After merging upstream/main, the local-model panels were refactored to
present Ollama as an external runtime (no in-app installer). The merge
preserved my i18n keys but kept their old values ('Ollama is not
installed', 'Install Ollama first'); the upstream unit tests assert the
new wording ('Ollama runtime unavailable', /Run Ollama first/i). Update
the existing key values to match.
…to all locales
- Restore the real pre-i18n wording for the Home promotional-credits
banner:
'home.banners.promoCreditsTitle' →
"You have {amount} of promotional credits."
'home.banners.promoCreditsBody' →
"Give OpenHuman a spin, and when you're ready for more,"
'home.banners.promoCreditsUsage' → "and get 10x more usage."
(Shipped as auto-generated placeholders in tinyhumansai#1986 — UI was rendering
the literal "Promo credits body" / "Promo credits usage".)
- Add the new `home.themeToggle.{toLight,toDark}` keys to all 10
locale chunks (ar, bn, en, es, fr, hi, id, it, pt, ru, zh-CN) so the
i18n coverage gate stays green after PR tinyhumansai#2095's theme toggle landed.
Summary
useT()hook so every user-visible string flows through the translation catalog.hi), Spanish (es), Arabic (ar), French (fr), Bengali (bn), Portuguese (pt), Russian (ru) — alongside the existing English, Indonesian, and Simplified Chinese.chunks/<locale>-N.ts) so future translation regens can fit inside the 32k Claude output-token cap.<html dir>inI18nProvider; extendsdetectLocale()andLanguageSelectwith all 10 BCP-47 prefixes and native-script labels.Problem
Most of the UI was still hardcoded English (89/231 components used
useT()before this PR), and only three locales shipped — none of them complete. Users in the largest non-English markets (Hindi, Spanish, Arabic, French, Bengali, Portuguese, Russian speakers) had no localised experience at all, and Indonesian / Mandarin had visible gaps once the existing surfaces grew. Adding more locales by hand at this scale is also infeasible without a refactor that respects model output limits.Solution
Four-wave migration:
Locale, added stub locale files (fall back to English via existingresolveEn()), wired language detection and<html lang dir>mirroring.t('namespace.key'). Per-area commits.en.tsinto 5 source chunks; each locale aggregates 5 translated chunk files. Dispatched 9 parallel translation agents (one per non-English locale) to fill the chunks. Existing Indonesian and Mandarin translations were recovered frommainand preserved verbatim; only the missing keys were filled.upstream/mainmid-flight (PR fix(i18n): complete zh-CN onboarding translations #1981 zh-CN onboarding fixes, ProgressIndicator a11y, Composio expired-auth state). Resolved 3 conflicts (OpenhumanLinkModal,ComposioConnectModal,zh-CN.ts) by adopting upstream's tighterAllowedPathtyping + newcomposio.authExpired/composio.reconnectkeys while keeping the chunked architecture; backported 31 polished zh-CN onboarding strings into the chunks.Caveat worth flagging: of the 812 newly-extracted English keys, ~340 fell back to a humanised label derived from the key name when the diff-mining couldn't recover the original literal. The values are reasonable English but worth a skim before broad release — the non-English locales were translated on top of them, so any English tweak should re-trigger the affected key in each locale chunk.
Submission Checklist
I18nContext.test.tsxcovers the lookup + fallback contract and remains green.diff-coverwill see the catalog edits as data lines.docs/TEST-COVERAGE-MATRIX.md.Impact
useTAPI unchanged. Existing Indonesian / Mandarin translations preserved verbatim, including the'Bahasa'/'Bersihkan Data Aplikasi'/'Quit'values asserted by the I18n test.<html dir>automatically when the locale is selected; verify visually on first run.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
i18n/full-coverage-10-languages6f237234Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckpnpm --filter openhuman-app test -- --run app/src/lib/i18n/(i18n suite green)Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
I18nContext.resolveEn(). Existing Indonesian / Mandarin translations recovered frommainand re-emitted verbatim into the new chunk layout.I18nContext.test.tsxassertion on Bahasa values still passes.Duplicate / Superseded PR Handling
Summary by CodeRabbit