feat(i18n): migrate to symbolic translation keys#75
Conversation
… keys
Replace all ~800 t() calls across 170+ files from raw English string keys
(e.g. t('Add to Cart')) to structured semantic keys (e.g. t('pos_cart.add_to_cart')).
- Bundle en.json fallback so symbolic keys never display to users
- Set fallbackLng: 'en' in i18next config
- Support plurals with proper _one/_other suffixes
- Handle regional locale fallback (e.g. fr_CA → fr) in RxDB backend
- Rename `count` → `shown` in non-plural "Showing X of Y" strings
- Add symbolic key validation to extract script
- Add docs/ to .gitignore
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds bundled English translations, converts many UI strings to namespaced i18n keys, refactors the RxDB translations backend to fetch exact and base-language fallbacks, updates i18n initialization, adjusts token-refresh handling for 403, and updates tests and extraction tooling. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(63,81,181,0.5)
participant Caller as i18n backend .read(lang, ns)
end
rect rgba(33,150,243,0.5)
participant Cache as translationsState (in-memory)
end
rect rgba(0,150,136,0.5)
participant CDN as Translation CDN (/{lang}/.../core.json)
end
Caller->>Cache: check cached[lang][namespace]
alt cache hit
Cache-->>Caller: return cached translations
else cache miss
Caller->>CDN: fetch /{lang}/.../{namespace}.json
alt exact locale found
CDN-->>Caller: JSON data
Caller->>Cache: cache[lang][namespace]=data
Caller-->>Caller: callback(data)
else exact locale missing or empty
Caller->>Caller: getBaseLanguage(lang)
alt base language exists
Caller->>CDN: fetch /{base}/.../{namespace}.json
alt base found
CDN-->>Caller: JSON data
Caller->>Cache: cache[lang][namespace]=data /* cache under original regional key */
Caller-->>Caller: callback(data)
else base not found
Caller-->>Caller: callback({})
end
else no base
Caller-->>Caller: callback({})
end
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
❌ Test Failures
|
| Package | Statements | Branches | Functions | Lines |
|---|---|---|---|---|
| @wcpos/core | 🔴 37.3% | 🔴 43.8% | 🔴 43.2% | 🔴 36.0% |
| @wcpos/components | 🔴 44.9% | 🟡 60.9% | 🔴 24.2% | 🔴 47.3% |
| @wcpos/database | 🔴 33.2% | 🔴 35.9% | 🔴 40.3% | 🔴 32.9% |
| @wcpos/hooks | 🔴 45.5% | 🔴 46.8% | 🔴 44.8% | 🔴 45.7% |
| @wcpos/utils | 🔴 35.0% | 🔴 0.0% | 🔴 50.0% | 🔴 33.3% |
| @wcpos/query | 🟡 68.0% | 🔴 52.6% | 🟡 66.1% | 🟡 68.0% |
| Average | 🔴 44.0% | 🔴 40.0% | 🔴 44.7% | 🔴 43.9% |
Coverage Legend
- 🟢 Good (≥80%)
- 🟡 Moderate (60-79%)
- 🔴 Needs improvement (<60%)
- ⚪ No coverage data
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/screens/main/contexts/use-pull-document.ts (1)
64-73:⚠️ Potential issue | 🟡 MinorPotential runtime error when accessing
err.messagewithout type checking.On line 65,
err.messageis accessed directly without verifying thaterris an Error object. Iferrisundefined,null, or a non-Error type, this will throw. The same file already uses the safe pattern on line 72.Proposed fix for consistent error handling
- syncLogger.error(t('common.failed_to_save_to_local_database', { error: err.message }), { + const errorMessage = err instanceof Error ? err.message : String(err); + syncLogger.error(t('common.failed_to_save_to_local_database', { error: errorMessage }), { showToast: true, saveToDb: true, context: { errorCode: ERROR_CODES.TRANSACTION_FAILED, documentId: id, collectionName: collection.name, - error: err instanceof Error ? err.message : String(err), + error: errorMessage, },
🤖 Fix all issues with AI agents
In `@packages/core/src/screens/auth/hooks/use-api-discovery.ts`:
- Around line 281-283: In the catch block inside the use-api-discovery hook,
guard access to err.message so accessing a non-Error doesn't throw; replace
err.message usage with a safe extraction (e.g., use optional chaining like
err?.message or coerce with String(err) and fallback to
t('auth.failed_to_discover_api_endpoints')) and then call setError(safeMessage)
and setStatus('error') — update the lines that reference err, setError, and
setStatus so they never assume err is an Error instance.
In `@packages/core/src/screens/auth/hooks/use-site-connect.ts`:
- Around line 263-266: In the catch block, accessing err.message can throw if
err is not an Error; update the logic around err, errorMessage, setError and
setStatus in use-site-connect (the catch where errorMessage is derived) to
safely extract a message: check if err is an instance of Error or a non-null
object with a string 'message' property and use that, otherwise fall back to
t('auth.failed_to_connect_to_site') (or String(err) as a final fallback); then
call setError(errorMessage) and setStatus('error') as before.
🧹 Nitpick comments (7)
packages/hooks/src/use-http-client/create-token-refresh-handler.ts (1)
161-172: Limit 403 handling to explicit auth-expired signals.Refreshing on every 403 can add an extra refresh+retry round-trip for genuine authorization failures and may repeat on each request. Consider guarding 403 handling with an explicit auth-expired marker (error code/header) to avoid unnecessary refresh cycles.
✅ Example guard (adjust codes/headers to your API)
- canHandle: (error) => { - const status = error.response?.status; - return status === 401 || status === 403; - }, + canHandle: (error) => { + const status = error.response?.status; + if (status === 401) return true; + if (status === 403) { + const code = error.response?.data?.code; + const authHeader = error.response?.headers?.['www-authenticate']; + return code === 'jwt_auth_invalid_token' || authHeader?.includes('invalid_token'); + } + return false; + },packages/core/src/screens/main/components/product/filter-bar/brands-pill.tsx (1)
56-56: Consider a more descriptive key name thancommon.id_2.The key
common.id_2with an interpolated{ id }parameter works functionally, but the_2suffix is opaque. A more descriptive key likecommon.id_labelorcommon.id_colonwould improve maintainability and make the translation file easier to navigate.packages/core/src/screens/main/components/product/tax-based-on/index.tsx (1)
22-29: Consider using a single translation key for the full label.String concatenation with
${t('common.tax_based_on')}: ${taxBasedOnSetting}may not translate well for languages with different grammatical structures or word orders. A dedicated key likecommon.tax_based_on_labelwith the setting as a placeholder would be more i18n-friendly.♻️ Suggested approach
- const taxBasedOnLabel = `${t('common.tax_based_on')}: ${taxBasedOnSetting}`; + const taxBasedOnLabel = t('common.tax_based_on_label', { setting: taxBasedOnSetting });This would require adding a new translation key like
"tax_based_on_label": "Tax based on: {{setting}}"to the locale file.packages/core/src/screens/main/logs/filter-bar.tsx (1)
29-38: Minor namespace inconsistency.The
errorlabel usescommon.errorwhile other log levels use thelogs.*namespace (logs.warning,logs.info, etc.). This is likely intentional ifcommon.erroris shared across the app, but for maintainability, consider usinglogs.errorto keep all log-level labels in the same namespace.packages/core/src/screens/auth/components/url-input.tsx (1)
45-45: Consider including the colon in the translation.Appending
:via string concatenation outside the translation can cause formatting issues in some locales (e.g., French uses a non-breaking space before colons). Consider including it in the translation string itself for proper localization.packages/core/src/screens/main/pos/cart/cells/fee-name.tsx (1)
54-54: Consider a more descriptive translation key name.The key
common.edit_2with the_2suffix is unconventional. A more self-documenting key likecommon.edit_itemorcommon.edit_namedwould better convey that this translation includes an interpolated name parameter (e.g., "Edit {name}").packages/core/src/contexts/translations/locales/en/core.json (1)
101-101: Normalize non‑ASCII en‑dash characters in key names.
Keys likecommon.enjoy_more_with_pro_–_upgradeembed a Unicode en dash in the key, which is easy to mistype and can lead to missing translations. Consider renaming to ASCII-only keys and updating call sites.♻️ Suggested key normalization
-"common.enjoy_more_with_pro_–_upgrade": "Enjoy More with Pro – Upgrade Today!", +"common.enjoy_more_with_pro_upgrade": "Enjoy More with Pro – Upgrade Today!", ... -"common.support_future_updates_–_get_pro": "Support Future Updates – Get Pro Now!", -"common.support_our_development_–_upgrade_to": "Support Our Development – Upgrade to Pro!", -"common.support_our_work_–_go_pro": "Support Our Work – Go Pro Today!", +"common.support_future_updates_get_pro": "Support Future Updates – Get Pro Now!", +"common.support_our_development_upgrade_to": "Support Our Development – Upgrade to Pro!", +"common.support_our_work_go_pro": "Support Our Work – Go Pro Today!",Also applies to: 260-262
🚀 Deployment Summary
🔗 Quick Links❌ Failed Tests (4 total)settings.spec.ts
📋 Full error details (first 5)settings.spec.ts - should change language to French and load translations from CDNProject: settings.spec.ts - should persist language after closing and reopening settingsProject: settings.spec.ts - should change language to French and load translations from CDNProject: settings.spec.ts - should persist language after closing and reopening settingsProject: 📸 Failure Screenshots🔗 Debug Links
🤖 Updated by GitHub Actions |
Cover the full fallback flow: fr_CA → fr → bundled en.json, including cache hits, 404s, empty responses, and network errors.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>






Summary
t()calls across 170+ files from English string keys (t('Add to Cart')) to structured semantic keys (t('pos_cart.add_to_cart'))en.jsonas fallback resource so symbolic keys never display to users_one/_othersuffixes (e.g.product_found_locally)fr_CA→fr)count→shownin 4 non-plural "Showing X of Y" locations to avoid triggering i18next plural resolutionKey format
feature.descriptorusing dots as visual separators (flat keys,keySeparator: false):Changes
t()calls rewritten to symbolic keyslocales/en/core.jsontranslations/index.tsxfallbackLng: 'en', bundledresourcesrxdb-backend.tsextract-js-strings.js.gitignoredocs/apps/electronTest plan
pnpm lint:fix && pnpm testFollow-up
wcpos/translationsrepo🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores