-
Notifications
You must be signed in to change notification settings - Fork 5
Localize the web and tauri apps #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This sets up the app to handle new translations that are put into `frontend/public/locales/`. When adding a new language, the Info.plist at `src-tauri/gen/apple/maple_iOS/Info.plist` also needs to have a new entry in the `CFBundleLocalizations` list. Next steps: 1. Convert all hardcoded strings in the app to use the localized versions 2. Add more translations This version has English, Spanish, French, and Portuguese. However, only a few of the strings in the UI are being pulled from the localized strings. See step 1 in "Next Steps"
WalkthroughIntroduces i18n: adds locale JSONs (en, fr, es, pt), an i18n initialization module, and integrates translations into TopNav and the index route. Updates app startup to await i18n init, adds frontend dependencies, documents localization, and wires Tauri localization plugin plus allowlist protocol changes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant App as App (React)
participant I18N as initI18n()
participant TP as Tauri Plugin (localization)
participant RES as Locale Files (*.json)
participant I as i18next
U->>App: Launch
App->>I18N: initI18n()
I18N->>TP: get_locale (try)
alt Plugin locale available
TP-->>I18N: locale (e.g., en-US)
else Plugin unavailable/error
I18N-->>I18N: use navigator.language or en-US
end
I18N->>RES: load short code JSON (e.g., en.json)
alt File found
RES-->>I18N: translations
else Missing
I18N->>RES: load English fallback
RES-->>I18N: translations or {}
end
I18N->>I: init({ lng, resources, fallbackLng: en })
I-->>I18N: ready
I18N-->>App: resolved
App-->>U: Render UI (t(...))
note over App,I: UI components call t('...') for labels
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Summary
This PR implements a comprehensive internationalization (i18n) infrastructure for the Maple AI application, adding support for English, Spanish, French, and Portuguese languages. The implementation includes several key components:
Core Infrastructure: The PR adds i18next and react-i18next dependencies and creates a sophisticated locale detection system in frontend/src/utils/i18n.ts that first attempts to detect the OS locale via Tauri's native localization plugin, then falls back to browser language detection, and ultimately defaults to English.
Translation Files: Four comprehensive JSON translation files are added in frontend/public/locales/ (en.json, es.json, fr.json, pt.json), each containing 178 lines of translations covering all major UI sections including authentication, chat functionality, teams, billing, settings, errors, and success messages.
Tauri Integration: The PR integrates tauri-plugin-localization in both the Rust backend (Cargo.toml and lib.rs) and enables the asset protocol in tauri.conf.json to allow secure loading of translation files. The iOS configuration is updated in Info.plist with CFBundleLocalizations entries for all supported languages.
Application Integration: The main app entry point (main.tsx) is modified to initialize i18n asynchronously before rendering React components, with proper error handling to ensure the app still works if localization fails. Two components (TopNav.tsx and routes/index.tsx) demonstrate the integration pattern using the useTranslation hook.
Documentation: A comprehensive README-localization.md file provides detailed setup instructions, technical architecture explanations, and troubleshooting guidance.
The implementation follows React i18n best practices with namespaced translation keys, dynamic loading of only required locale files, and graceful fallback mechanisms. The architecture supports both web and native (desktop/mobile) environments through Tauri's cross-platform capabilities.
Confidence score: 4/5
- This PR establishes solid i18n infrastructure with minimal risk of breaking existing functionality
- Score reflects the comprehensive implementation but notes that most UI strings remain unconverted as acknowledged in the PR description
- Pay special attention to the async initialization in
main.tsxand ensure proper testing across different locale scenarios
14 files reviewed, 1 comment
| <div className="flex items-center gap-6 sm:gap-10"> | ||
| <NavLink to="/pricing">Pricing</NavLink> | ||
| <NavLink to="/pricing">{t('navigation.pricing')}</NavLink> | ||
| <NavLink to="/proof">Proof</NavLink> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: The 'Proof' navigation item is not localized while others are - this creates inconsistent user experience across languages
| <NavLink to="/proof">Proof</NavLink> | |
| <NavLink to="/proof">{t('navigation.proof')}</NavLink> |
There was a problem hiding this 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 (19)
frontend/package.json (1)
20-21: i18n compatibility & configuration verified
- Confirmed in
frontend/package.jsonthat you’re using
• "i18next": "^23.6.0"
• "react-i18next": "^13.0.0"
These versions are fully compatible with React 18 (react-i18next’s peer requirement is React >= 16.8 and v13 explicitly requires i18next >= 23.x).- Checked your initialization in
frontend/src/utils/i18n.ts(around line 76):
•interpolation: { escapeValue: false }is already set, which is required for React.Optional: to pick up the very latest patches and improvements, consider bumping to the latest minor releases on each branch:
npm install i18next@^23.16.8 react-i18next@^13.5.0This isn’t strictly necessary—your current setup is correct—but will ensure you have the most recent fixes.
frontend/public/locales/fr.json (3)
3-6: French grammar nit: agree “IA” (feminine) with “privée”.Recommended:
- title: "Maple - Chat IA privée"
- description: "Chat IA privée"
Apply this diff:
- "title": "Maple - Chat IA Privé", + "title": "Maple - Chat IA privée", @@ - "description": "Chat IA Privé" + "description": "Chat IA privée"
16-34: Consistency: prefer one term for sign-in vs. login.You mix “Se connecter” and “Connexion” across keys (signIn/login). That’s fine if keys map to different contexts, but ensure the UI uses the intended phrasing consistently.
168-177: Placeholders preserved—good. Consider adding l10n for brand/legal strings separately.Footer strings keep {{year}} intact. If legal copy changes per locale (e.g., localized privacy/terms labels already here), ensure routes/links also switch per locale when applicable.
frontend/public/locales/pt.json (2)
3-6: Portuguese copy nit: align title and description phrasing.Title reads “Chat IA Privado” but description uses “Chat com IA Privada.” Consider standardizing; suggested both use “com IA Privada.”
Apply this diff:
- "title": "Maple - Chat IA Privado", + "title": "Maple - Chat com IA Privada", @@ - "description": "Chat com IA Privada" + "description": "Chat com IA Privada"
51-63: Small idiomatic tweak (optional)."Entrar em equipe" is understandable; in pt-BR "Entrar na equipe" is a bit more idiomatic. Up to your target locale.
frontend/public/locales/es.json (1)
36-49: Spanish copy nits: more idiomatic phrasing.
- Prefer “Nuevo chat” over “Chat nuevo”.
- “Unirse a un equipo” instead of “Unirse a equipo”.
Apply this diff:
- "newChat": "Chat nuevo", + "newChat": "Nuevo chat", @@ - "joinTeam": "Unirse a equipo", + "joinTeam": "Unirse a un equipo",frontend/public/locales/en.json (1)
1-178: Continue strengthening i18n coverage – numerous hardcoded UI labels detectedInvestigation with ripgrep revealed many hardcoded English strings across components, indicating gaps between the locale file and actual usage. To fully localize the UI and prevent drift, please extract the following into
en.json(and other locales), then replace literals witht('…')calls:• Navigation
– components/TopNav.tsx: “Proof” (lines 56–57, 106–108), “Guides” (64–65, 119–120)
– components/Footer.tsx: “Pricing”, “Downloads”, “Security Proof”, “Teams”, “About” (lines 60–61, 66–67, 72–73, 78–79, 112–113)• Pages & Headers
– routes/pricing.tsx & Marketing.tsx: headings like “Pricing”, “Simple, Transparent Pricing” (pricing.tsx:556–557; Marketing.tsx:634–636)
– routes/proof.tsx: “Proof of Security” header (185–187)
– routes/about.tsx: “About Maple AI” title (19–21)
– routes/teams.tsx: hero text “Secure AI for Teams” (41–43), section titles/descriptions (59–61, 75–77, 101–103, 206–208, 270–271)
– routes/downloads.tsx: “Desktop Downloads Section” comment (75–77)• Chat & Sidebar
– components/Sidebar.tsx: “New Chat” button label (134–137)
– components/ChatHistoryList.tsx: “Rename Chat”, “Delete Chat” menu items (130–135)
– ChatBox.tsx: capacity messages (“Chat is at capacity. Compress to continue.” at 124–126; “Chat is too long to continue.” at 715–717)• Dialogs & Forms
– components/RenameChatDialog.tsx: “Chat title cannot be empty.” error, dialog header/title/description, button labels “Rename Chat”/“Renaming…” (56–58, 84–85, 93–95, 116–118)• Config & Debug
– config/pricingConfig.tsx:{ text: "Search Chat History" }(lines 39–41)
– components/BillingDebugger.tsx: “Can Chat” label (95–97)Other occurrences include Apple Sign-In log strings, form error messages, and marketing CTA text (e.g., “Start Secure Chat”, “Encrypted Chat”, “Ready to Chat Securely?”). Please audit these for localization as well.
Additionally:
- De-duplicate overlapping keys (e.g., use
common.emailvs.auth.email) to maintain consistency.- Add new keys for “navigation.proof”, “navigation.guides”, “app.logoAlt”:
{ "navigation": { "proof": "Proof", "guides": "Guides" }, "app": { "logoAlt": "Maple AI logo" } }- Ensure runtime fallback to English is tested per
i18n.tscomments so missing translations render the English strings, not key paths.By consolidating and centralizing UI text in your locale files, you’ll avoid mixed-language UIs and simplify future translations.
frontend/src/routes/index.tsx (2)
11-11: Use project-standard double quotes for imports and translation keysFollow the repo guideline for double quotes in TS/TSX. Also OK on the i18n usage of t('app.description').
Apply:
-import { useTranslation } from 'react-i18next'; +import { useTranslation } from "react-i18next"; ... - const { t } = useTranslation(); + const { t } = useTranslation(); ... - {t('app.description')} + {t("app.description")}Also applies to: 54-54, 167-167
155-165: Localize image alt text for accessibilityAlt text is currently hardcoded. Add an i18n key (e.g., app.logoAlt) and use it here for consistency with the new localization system.
Example (non-diff, since outside changed lines):
<img src="/maple-logo.svg" alt={t("app.logoAlt")} className="w-[10rem] hidden dark:block" /> <img src="/maple-logo-dark.svg" alt={t("app.logoAlt")} className="w-[10rem] block dark:hidden filter drop-shadow-sm" />frontend/src/components/TopNav.tsx (1)
7-7: Standardize on double quotes and ensure all nav labels are localized
- Switch single quotes to double quotes per style guide.
- Good use of i18n for Pricing/Teams/Chat/Login.
- Remaining labels "Proof" and "Guides" are still hardcoded; add keys (navigation.proof, navigation.guides) and translate both desktop and mobile menus.
Apply:
-import { useTranslation } from "react-i18next"; +import { useTranslation } from "react-i18next"; ... - const { t } = useTranslation(); + const { t } = useTranslation(); ... - <NavLink to="/pricing">{t('navigation.pricing')}</NavLink> + <NavLink to="/pricing">{t("navigation.pricing")}</NavLink> ... - <NavLink to="/teams">{t('navigation.teams')}</NavLink> + <NavLink to="/teams">{t("navigation.teams")}</NavLink> ... - {t('navigation.chat')} + {t("navigation.chat")} ... - {t('auth.login')} + {t("auth.login")} ... - {t('navigation.pricing')} + {t("navigation.pricing")} ... - {t('navigation.teams')} + {t("navigation.teams")}Additionally localize remaining items (illustrative, outside changed lines):
<NavLink to="/proof">{t("navigation.proof")}</NavLink> ... <a ...>{t("navigation.guides")}</a>Also applies to: 10-10, 55-55, 57-57, 76-76, 83-83, 104-104, 111-111
frontend/src/utils/i18n.ts (2)
1-1: Tighten typings: use i18next Resource and consistent quote styleImport and use i18next’s Resource type for
loadResourcesto improve type safety. Also switch to double quotes per the project’s style.Apply:
-import i18next, { InitOptions } from 'i18next'; +import i18next, { InitOptions, Resource } from "i18next";And adjust the function signature (if applying the previous change, this aligns naturally):
async function loadResources(requested: string): Promise<Resource> { ... }
72-79: Strengthen i18n init for partial locales and supportedLngsSince only a subset of strings is localized initially, ensure fallback works reliably and constrain to supported languages.
Apply:
const options: InitOptions = { lng: short, fallbackLng: 'en', resources, interpolation: { escapeValue: false }, react: { useSuspense: false }, - debug: import.meta.env.DEV // Enable debug logging in development + debug: import.meta.env.DEV, // Enable debug logging in development + supportedLngs: ["en", "es", "fr", "pt"], + load: "currentOnly" };frontend/src/main.tsx (1)
8-9: Apply double quotes in logs to match code styleMinor style nits; switch to double quotes.
- console.log('[main] i18n initialized, rendering app...'); + console.log("[main] i18n initialized, rendering app..."); ... - console.error('[main] Failed to initialize i18n:', error); + console.error("[main] Failed to initialize i18n:", error);Also applies to: 21-21
frontend/README-localization.md (5)
14-19: Bring “Current Supported Languages” in sync with this PR (add Portuguese; avoid “Complete” claims).Per the PR summary, Portuguese is included and only a subset of UI strings are localized. Let’s reflect that to prevent confusion during QA.
Apply:
-## Current Supported Languages - -- **English** (`en`) - Default and fallback language -- **French** (`fr`) - Complete translations -- **Spanish** (`es`) - Complete translations +## Current Supported Languages + +- **English** (`en`) — Default / fallback +- **French** (`fr`) — Partial coverage (strings migrating) +- **Spanish** (`es`) — Partial coverage (strings migrating) +- **Portuguese** (`pt`) — Partial coverage (strings migrating)
22-37: Fix fenced code language and include pt.json in the file tree.Addresses markdownlint MD040 and keeps the tree aligned with the assets shipped in this PR.
-``` +```text frontend/ ├── public/locales/ # Translation files │ ├── en.json # English (default) │ ├── fr.json # French -│ └── es.json # Spanish +│ ├── es.json # Spanish +│ └── pt.json # Portuguese ├── src/ │ ├── utils/i18n.ts # i18n configuration │ ├── main.tsx # i18n initialization │ └── components/ # Components using translations └── src-tauri/ ├── Cargo.toml # Rust dependencies ├── src/lib.rs # Plugin registration ├── tauri.conf.json # Asset protocol config └── gen/apple/maple_iOS/Info.plist # iOS language declarations -``` +```
46-61: Use “jsonc” for illustrative JSON-with-comments snippet.The snippet contains comments; labeling it as jsonc avoids “invalid JSON” confusion in editors and linters.
-```json +```jsonc { "app": { "title": "Maple - Private KI-Chat", "welcome": "Willkommen bei Maple", "description": "Private KI-Chat mit vertraulicher Datenverarbeitung" }, "auth": { "signIn": "Anmelden", "signOut": "Abmelden", "email": "E-Mail", "password": "Passwort" } // ... continue with all keys }--- `67-75`: **Include Portuguese in the Info.plist example and clarify placeholder.** Keeps the example aligned with shipped locales while still showing how to add more. ```diff <key>CFBundleLocalizations</key> <array> <string>en</string> <string>fr</string> <string>es</string> - <string>de</string> <!-- Add your new language --> + <string>pt</string> + <string>de</string> <!-- Example of adding a new language --> </array>
151-156: Reword platform “Status” to set correct expectations (pipeline ready, strings migrating).Right now only a subset of strings are localized; “Fully supported” may be misread as “100% translated.”
-| Platform | Locale Detection | Status | -|----------|------------------|--------| -| **Desktop** (Windows/macOS/Linux) | ✅ Native OS locale | Fully supported | -| **iOS** | ✅ Device language | Fully supported | -| **Web** | ✅ Browser language | Fallback only | +| Platform | Locale Detection | Status | +|----------|------------------|--------| +| **Desktop** (Windows/macOS/Linux) | ✅ Native OS locale | Localization pipeline enabled; strings migrating | +| **iOS** | ✅ Device language | Localization pipeline enabled; strings migrating | +| **Web** | ✅ Browser language | Fallback path enabled; strings migrating |
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
frontend/bun.lockbis excluded by!**/bun.lockbfrontend/src-tauri/gen/apple/maple_iOS/Info.plistis excluded by!**/gen/**
📒 Files selected for processing (13)
frontend/README-localization.md(1 hunks)frontend/package.json(1 hunks)frontend/public/locales/en.json(1 hunks)frontend/public/locales/es.json(1 hunks)frontend/public/locales/fr.json(1 hunks)frontend/public/locales/pt.json(1 hunks)frontend/src-tauri/Cargo.toml(1 hunks)frontend/src-tauri/src/lib.rs(2 hunks)frontend/src-tauri/tauri.conf.json(1 hunks)frontend/src/components/TopNav.tsx(4 hunks)frontend/src/main.tsx(1 hunks)frontend/src/routes/index.tsx(3 hunks)frontend/src/utils/i18n.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use 2-space indentation, double quotes, and a 100-character line limit for formatting
Use camelCase for variable and function names
Use try/catch with specific error types for error handling
Files:
frontend/src/routes/index.tsxfrontend/src/main.tsxfrontend/src/components/TopNav.tsxfrontend/src/utils/i18n.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript typing and avoid
anywhen possible
Files:
frontend/src/routes/index.tsxfrontend/src/main.tsxfrontend/src/components/TopNav.tsxfrontend/src/utils/i18n.ts
🧬 Code graph analysis (1)
frontend/src/main.tsx (2)
frontend/src/utils/i18n.ts (1)
initI18n(62-83)frontend/src/app.tsx (1)
App(48-107)
🪛 LanguageTool
frontend/README-localization.md
[grammar] ~16-~16: There might be a mistake here.
Context: ...* (en) - Default and fallback language - French (fr) - Complete translations ...
(QB_NEW_EN)
[grammar] ~17-~17: There might be a mistake here.
Context: ...French* (fr) - Complete translations - Spanish (es) - Complete translations...
(QB_NEW_EN)
[grammar] ~79-~79: There might be a mistake here.
Context: ...the Implementation 1. Development: bun tauri dev - Change your OS language settings - Re...
(QB_NEW_EN)
[grammar] ~83-~83: There might be a mistake here.
Context: ...pp to see the new language 2. iOS: bun tauri build --target ios - Build and run in iOS Simulator - Chan...
(QB_NEW_EN)
[grammar] ~130-~130: There might be a mistake here.
Context: ...ependencies - Frontend: i18next, react-i18next - Backend: tauri-plugin-localization ...
(QB_NEW_EN)
[grammar] ~136-~136: There might be a mistake here.
Context: ...dering 2. i18n.ts resolves the locale using Tauri plugin 3. Appropriate JSON file i...
(QB_NEW_EN)
[grammar] ~151-~151: There might be a mistake here.
Context: ...| Platform | Locale Detection | Status | |----------|------------------|--------|...
(QB_NEW_EN)
[grammar] ~152-~152: There might be a mistake here.
Context: ...|----------|------------------|--------| | Desktop (Windows/macOS/Linux) | ✅ ...
(QB_NEW_EN)
[grammar] ~153-~153: There might be a mistake here.
Context: ...| ✅ Native OS locale | Fully supported | | iOS | ✅ Device language | Fully su...
(QB_NEW_EN)
[grammar] ~154-~154: There might be a mistake here.
Context: ... | ✅ Device language | Fully supported | | Web | ✅ Browser language | Fallbac...
(QB_NEW_EN)
[grammar] ~164-~164: There might be a mistake here.
Context: ...t the app after changing OS language 4. Check browser console for i18n loading errors...
(QB_NEW_EN)
[grammar] ~168-~168: There might be a mistake here.
Context: ...ns 1. Compare your JSON structure with en.json 2. Ensure all keys match exactly (case-sens...
(QB_NEW_EN)
[grammar] ~175-~175: There might be a mistake here.
Context: ...1. Ensure Xcode project is regenerated: bun tauri build --target ios 2. Check that CFBundleLocalizations is pr...
(QB_NEW_EN)
[grammar] ~189-~189: There might be a mistake here.
Context: ...ement plural forms for complex languages - [ ] Add context-aware translations - [ ]...
(QB_NEW_EN)
[grammar] ~190-~190: There might be a mistake here.
Context: ...ges - [ ] Add context-aware translations - [ ] Create translation management workfl...
(QB_NEW_EN)
[grammar] ~191-~191: There might be a mistake here.
Context: ...] Create translation management workflow - [ ] Add RTL language support (Arabic, He...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
frontend/README-localization.md
22-22: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (7)
frontend/public/locales/fr.json (1)
1-178: Locale Key Parity Confirmed
The parity check script shows no missing or extra keys betweenen.json,es.json,fr.json, andpt.json. All locale files are in sync—no further changes needed.frontend/public/locales/es.json (1)
169-177: Placeholders and punctuation look good.Keys retain {{year}} and consistent punctuation. No action needed.
frontend/src/main.tsx (1)
6-19: Good: defer render until i18n is initializedThe startup sequence correctly waits on initI18n before rendering, avoiding flashes of untranslated content.
frontend/README-localization.md (1)
181-185: FOUC prevention verifiedConfirmed that in
frontend/src/main.tsx,initI18n()is awaited before rendering<App />, ensuring the first render is gated until localization is initialized. The catch block logs a warning and still renders if initialization fails, matching the doc’s guarantee. Infrontend/src/utils/i18n.ts, the code correctly attempts the Tauri plugin then falls back tonavigator.languagewithout throwing, covering both desktop and web cases.No changes required.
frontend/src-tauri/Cargo.toml (1)
32-32: Could you please share the exact crates.io or GitHub URL for tauri-plugin-localization v0.2? With that link, I can verify its declared Tauri 2.4.x compatibility, check iOS support in its docs/CI matrix, and surface any known iOS build caveats.frontend/src-tauri/src/lib.rs (2)
25-25: Localization plugin wired on desktop: LGTM.Ordering after tauri_plugin_os::init() is sensible. No side-effects expected with the existing plugin chain.
208-210: Verify iOS build with localization pluginConfirmed that
tauri-plugin-localization = "0.2"is declared in frontend/src-tauri/Cargo.toml (line 32). To catch any cfg-gating or dependency issues on iOS, please run a smoke-check build:
- File: frontend/src-tauri/Cargo.toml (line 32)
- Dependency: tauri-plugin-localization = "0.2"
Recommended commands:
rustup target add aarch64-apple-ios cd frontend/src-tauri cargo check --target aarch64-apple-ios
| "tauri": { | ||
| "allowlist": { | ||
| "protocol": { | ||
| "asset": true | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Check whether allowlist.protocol.asset is still effective on Tauri v2; add CSP allowances if loading asset:// at runtime.
In Tauri v2, parts of the legacy allowlist were reworked toward capabilities. Please double-check that "tauri.allowlist.protocol.asset": true is recognized in your current CLI/runtime; otherwise this block may be a no-op. If your i18n loader fetches JSON via the asset:// protocol at runtime, add the scheme to CSP (e.g., connect-src/img-src) to avoid CSP violations.
Would you like me to propose a follow-up patch adjusting CSP and, if needed, migrating this to the recommended v2 capabilities config once confirmed?
🌐 Web query:
For Tauri v2, is the "tauri.allowlist.protocol.asset" config still supported, or should the asset protocol be enabled via capabilities? Provide the canonical docs section.
💡 Result:
Short answer: tauri.allowlist.protocol.asset (the v1 allowlist key) is not used in Tauri v2. In v2 you enable the asset protocol via the security.assetProtocol config (app.security.assetProtocol.enable and scope) — capabilities are a separate ACL system for plugin/IPC permissions. (v2.tauri.app)
Canonical docs sections (for copy/reading):
- "Migrate Permissions" — explains v1 allowlist → v2 capabilities change. (v2.tauri.app)
- "AssetProtocolConfig" / SecurityConfig — shows the assetProtocol object (enable, scope) under the v2 config reference. (v2.tauri.app)
- core.convertFileSrc() — notes you must set app.security.assetProtocol.enable = true and add asset: to your CSP to use the asset protocol. (v2.tauri.app)
If you want, I can paste the exact small snippets from those sections or show an example tauri.conf.json snippet that enables the asset protocol + capability example. Which would you prefer?
Citations:
- 1: https://v2.tauri.app/start/migrate/from-tauri-1/?utm_source=chatgpt.com
- 2: https://v2.tauri.app/start/migrate/from-tauri-1/?utm_source=chatgpt.com
- 3: https://v2.tauri.app/reference/config/?utm_source=chatgpt.com
- 4: https://v2.tauri.app/reference/javascript/api/namespacecore/?utm_source=chatgpt.com
Migrate from deprecated allowlist.protocol.asset to the v2 security.assetProtocol config
Please replace the legacy v1 allowlist key with the new v2 security setting and ensure your CSP permits the asset:// scheme:
• Remove this block (no-op in v2):
"tauri": {
"allowlist": {
"protocol": {
"asset": true
}
}
}• Add under "tauri" → "security" the AssetProtocolConfig:
"tauri": {
+ "security": {
+ "assetProtocol": {
+ "enable": true,
+ "scope": ["asset://*"]
+ }
+ },
// …other settings…
} – In v2 the old tauri.allowlist.protocol.asset key is ignored; you now enable the asset protocol via app.security.assetProtocol .
• Update your Content Security Policy to include the asset: scheme, for example:
"csp": "default-src 'self'; connect-src 'self' asset://*; img-src 'self' asset://*;" – core.convertFileSrc() will throw CSP errors unless app.security.assetProtocol.enable is true and asset: is allowed .
Let me know if you’d like a full example snippet or additional guidance on CSP rules.
🤖 Prompt for AI Agents
In frontend/src-tauri/tauri.conf.json around lines 49 to 55, the config still
uses the deprecated v1 allowlist.protocol.asset setting; remove that block and
instead add the v2 security.assetProtocol config under "tauri" (enable
app.security.assetProtocol.enable and configure any needed options), and update
the CSP string to include the asset: scheme (e.g., add asset://* or asset: to
connect-src/img-src/default-src as appropriate) so core.convertFileSrc() will
not trigger CSP errors.
| const localeModules = import.meta.glob('/public/locales/*.json') as Record< | ||
| string, | ||
| () => Promise<{ default: unknown }> | ||
| >; | ||
|
|
||
| async function loadResources(requested: string) { | ||
| const short = requested.split('-')[0]; // en-US → en | ||
| const path = `/public/locales/${short}.json`; | ||
|
|
||
| console.log('[i18n] Looking for locale file:', path); | ||
| console.log('[i18n] Available locale modules:', Object.keys(localeModules)); | ||
|
|
||
| const loader = localeModules[path]; | ||
| if (!loader) { | ||
| console.warn(`[i18n] Locale ${short} not found – falling back to English`); | ||
| const englishLoader = localeModules['/public/locales/en.json']; | ||
| if (!englishLoader) { | ||
| console.error('[i18n] English fallback not found!'); | ||
| return { en: { translation: {} } }; | ||
| } | ||
| const mod = await englishLoader(); | ||
| return { en: { translation: mod.default as Record<string, unknown> } }; | ||
| } | ||
|
|
||
| const mod = await loader(); | ||
| return { [short]: { translation: mod.default as Record<string, unknown> } }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
import.meta.glob cannot load from /public; resources will be empty at runtime
Vite’s public/ folder isn’t part of the module graph, so import.meta.glob("/public/locales/*.json") returns no modules. As a result, loader is undefined and i18n ends up with { en: {} }, breaking translations.
Fix by fetching static JSON from /locales/*.json (public root) and load both the requested language and English for proper fallback.
Apply:
-const localeModules = import.meta.glob('/public/locales/*.json') as Record<
- string,
- () => Promise<{ default: unknown }>
->;
-
-async function loadResources(requested: string) {
- const short = requested.split('-')[0]; // en-US → en
- const path = `/public/locales/${short}.json`;
-
- console.log('[i18n] Looking for locale file:', path);
- console.log('[i18n] Available locale modules:', Object.keys(localeModules));
-
- const loader = localeModules[path];
- if (!loader) {
- console.warn(`[i18n] Locale ${short} not found – falling back to English`);
- const englishLoader = localeModules['/public/locales/en.json'];
- if (!englishLoader) {
- console.error('[i18n] English fallback not found!');
- return { en: { translation: {} } };
- }
- const mod = await englishLoader();
- return { en: { translation: mod.default as Record<string, unknown> } };
- }
-
- const mod = await loader();
- return { [short]: { translation: mod.default as Record<string, unknown> } };
-}
+async function loadResources(requested: string) {
+ const short = requested.split("-")[0]; // en-US → en
+
+ async function loadJson(code: string): Promise<Record<string, unknown>> {
+ try {
+ const res = await fetch(`/locales/${code}.json`, { cache: "no-store" });
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ return (await res.json()) as Record<string, unknown>;
+ } catch (err) {
+ console.warn(`[i18n] Failed to load /locales/${code}.json`, err);
+ return {};
+ }
+ }
+
+ // Always include English for partial translations to fallback correctly
+ const [primary, english] = await Promise.all([
+ loadJson(short),
+ short === "en" ? Promise.resolve({}) : loadJson("en")
+ ]);
+
+ const resources: Record<string, { translation: Record<string, unknown> }> = {};
+ resources[short] = { translation: primary };
+ if (short !== "en") resources["en"] = { translation: english };
+
+ return resources;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const localeModules = import.meta.glob('/public/locales/*.json') as Record< | |
| string, | |
| () => Promise<{ default: unknown }> | |
| >; | |
| async function loadResources(requested: string) { | |
| const short = requested.split('-')[0]; // en-US → en | |
| const path = `/public/locales/${short}.json`; | |
| console.log('[i18n] Looking for locale file:', path); | |
| console.log('[i18n] Available locale modules:', Object.keys(localeModules)); | |
| const loader = localeModules[path]; | |
| if (!loader) { | |
| console.warn(`[i18n] Locale ${short} not found – falling back to English`); | |
| const englishLoader = localeModules['/public/locales/en.json']; | |
| if (!englishLoader) { | |
| console.error('[i18n] English fallback not found!'); | |
| return { en: { translation: {} } }; | |
| } | |
| const mod = await englishLoader(); | |
| return { en: { translation: mod.default as Record<string, unknown> } }; | |
| } | |
| const mod = await loader(); | |
| return { [short]: { translation: mod.default as Record<string, unknown> } }; | |
| } | |
| async function loadResources(requested: string) { | |
| const short = requested.split("-")[0]; // en-US → en | |
| async function loadJson(code: string): Promise<Record<string, unknown>> { | |
| try { | |
| const res = await fetch(`/locales/${code}.json`, { cache: "no-store" }); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| return (await res.json()) as Record<string, unknown>; | |
| } catch (err) { | |
| console.warn(`[i18n] Failed to load /locales/${code}.json`, err); | |
| return {}; | |
| } | |
| } | |
| // Always include English for partial translations to fallback correctly | |
| const [primary, english] = await Promise.all([ | |
| loadJson(short), | |
| short === "en" ? Promise.resolve({}) : loadJson("en") | |
| ]); | |
| const resources: Record<string, { translation: Record<string, unknown> }> = {}; | |
| resources[short] = { translation: primary }; | |
| if (short !== "en") resources["en"] = { translation: english }; | |
| return resources; | |
| } |
This sets up the app to handle new translations that are put into
frontend/public/locales/. When adding a new language, the Info.plist atsrc-tauri/gen/apple/maple_iOS/Info.plistalso needs to have a new entry in theCFBundleLocalizationslist.Next steps:
This version has English, Spanish, French, and Portuguese. However, only a few of the strings in the UI are being pulled from the localized strings. See step 1 in "Next Steps"
Summary by CodeRabbit
New Features
Documentation