feat(i18n): localize hardcoded strings across components - #83
Conversation
- update-checker: full update flow (toast, dialog, buttons, error mapping) now uses t() - sync-dialog: 33 hardcoded strings localized (title, labels, dropdowns, badges, table headers, toasts) - integrated-file-browser: drag-overlay, context menu, paste count, file-info toast localized - error-boundary: fallback error card localized via i18n singleton (class component) - settings-modal: editor tab-size options and scroll aria-labels localized - App.tsx: connection toasts and ErrorBoundary labels localized - connection-tabs / network-monitor: localize unused components for i18n completeness - locales: add 84 en/zh key pairs, parity maintained (1044 keys each) Test: 566 tests pass, 1 new test covers i18n key completeness and locale parity
The follow/Home-navigation test waits for the breadcrumb to render '/home' with findByTitle, whose default 1000ms timeout is occasionally exceeded on slow CI runners (Windows), producing a pre-existing flake unrelated to the i18n change. Bump the wait to 5000ms in the affected tests. Test: 566 tests pass, 0 new tests
There was a problem hiding this comment.
Pull request overview
This PR routes user-visible strings through the i18n translation layer across several UI components (notably the update-check flow), adds new locale keys while keeping en/zh parity, and updates/extends tests to validate i18n coverage.
Changes:
- Replaced hardcoded UI/toast strings with
t('...')/i18n.t('...')across multiple components (update checker, sync dialog, file browser, error boundary, etc.). - Expanded
en.jsonandzh-CN.jsonwith new key pairs to support the newly localized strings (maintaining parity). - Updated and added tests, including a new test intended to assert translation-key completeness across the source tree.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/locales/zh-CN.json | Adds new zh-CN translations for newly localized UI strings; keeps parity with en.json. |
| src/locales/en.json | Adds new English translations / keys used by updated components and tests. |
| src/components/update-checker.tsx | Localizes update-check toasts/dialog text and error mappings via t(). |
| src/components/sync-dialog.tsx | Localizes sync dialog UI strings and toasts via t(). |
| src/components/settings-modal.tsx | Localizes scroll button aria-labels and tab-size options. |
| src/components/network-monitor.tsx | Localizes currently-disabled network monitor placeholder copy. |
| src/components/integrated-file-browser.tsx | Localizes drag overlay, context menu labels, and file-info toast formatting. |
| src/components/error-boundary.tsx | Localizes error boundary fallback UI using the i18n singleton. |
| src/components/connection-tabs.tsx | Localizes context menu items in connection tabs. |
| src/App.tsx | Localizes connection-related toasts and error boundary labels. |
| src/tests/update-checker.test.tsx | Updates assertions to match i18n-backed strings for update-check flow. |
| src/tests/integrated-file-browser-keyboard.test.tsx | Increases timeouts to reduce CI flakiness. |
| src/tests/i18n.test.ts | Adds a new locale parity / “all used keys exist” completeness test. |
Suppressed comments (1)
src/tests/i18n.test.ts:42
require()and__dirnameare not available in ESM modules (this repo is "type": "module"), so this new completeness test will throw at runtime. Use the ESM imports above and derive the src root fromprocess.cwd()(orimport.meta.url) instead.
const fs = require('node:fs');
const path = require('node:path');
const srcDir = path.resolve(__dirname, '..');
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import i18n from '../lib/i18n'; | ||
| import { changeLanguage, applyLanguageFromPreference, getLanguagePreference, AUTO } from '../lib/i18n'; | ||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||
| import en from '../locales/en.json'; | ||
| import zhCN from '../locales/zh-CN.json'; |
| <ContextMenuItem onClick={handlePasteFiles}> | ||
| <ClipboardPaste className="mr-2 h-4 w-4" /> | ||
| {t('fileBrowser.contextMenu.paste')} {clipboard.files.length} item(s) | ||
| {t('fileBrowser.contextMenu.paste')} {t('fileBrowser.contextMenu.pasteCount', { count: clipboard.files.length })} |
- i18n.test.ts: replace CJS require()/__dirname with ESM imports and process.cwd() to avoid ESM-incompatible globals in a "type": "module" repo - integrated-file-browser: replace space-joined paste translation with a single plural key (pasteWithCount) so each locale controls word order Test: 566 tests pass, 0 new tests
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/locales/en.json:763
- These new messages preserve the
(s)hack and also render1 itemsin singular cases. Define plural variants and pass the relevant value as i18next'scount(the error summary may need separate pluralized fragments because it contains two counts). Nearbybadge*keys demonstrate the established_one/_otherpattern.
"toastComplete": "Sync complete: {{count}} item(s) synchronized",
"toastFinishedWithErrors": "Sync finished with {{errorCount}} error(s) out of {{processedItems}} items",
src/locales/en.json:760
- This count-aware label renders
Sync (1 items). Use i18next_one/_othervariants in both locale files, as the badge keys below already do, so singular counts are grammatical.
This issue also appears on line 762 of the same file.
"syncItems": "Sync ({{count}} items)",
src/components/sync-dialog.tsx:354
- The toast title is localized, but its description still receives the frontend fallbacks
"Upload failed"and"Download failed"from lines 323 and 345. When the backend returnssuccess: falsewithout an error, Chinese users therefore still see English in this sync toast. Add translated fallback keys while continuing to display backend-provided error text verbatim.
toast.error(t('syncDialog.toastFailed', { path: entry.relativePath }), {
description: err instanceof Error ? err.message : String(err),
Resolve conflict in integrated-file-browser-keyboard.test.tsx: keep the upstream findByTitle 5s timeout (already merged in GOODBOY008#83) plus our test-level 15s budget for the terminal-follow test.
Summary
Users running R-Shell with "follow system" language on a Chinese system saw English text in several places — most notably the update-check flow (toasts + dialog). The i18n mechanism itself was intact: the AUTO preference correctly resolves to zh-CN via get_system_locale, and both locale files had full, aligned keys. The problem was purely hardcoded English strings — components written/rewritten without routing user-visible text through
t().Root cause
Several components (update-checker, sync-dialog, integrated-file-browser, error-boundary, settings-modal, App.tsx, plus two unused ones) emitted raw English literals for toasts, titles, buttons, table headers, and error fallbacks instead of using the
t('key')helper.Changes
Localized 8 components — before → after on a Chinese system:
Checking for updates…,You are up to date.,Download update,Restart now,Later,Update ready to install… (18 strings)Directory Synchronization,Local Directory,Compare by,Select all,Sync (N items),Path/Actiontable headers, toasts … (33 strings)Drop files or folders to upload,Open,Edit,Open Folder,item(s), file-info toast (7 strings)2 spaces/4 spaces/8 spaces,Scroll left/rightaria-labelsSomething went wrong,An unexpected error occurred,RetryConnection Not Found,Already Connected,Switched to existing X connectionupdateChecker.*dictionary keys the component previously ignored.i18n.t) to avoid changing its named export.Testing
t()key used in source resolves in the locale files (plural/context variants included) and that en/zh stay in parity.pnpm i18n:checkpasses (key parity).npx tsc --noEmitpasses.