Skip to content

Conversation

@marksftw
Copy link
Contributor

@marksftw marksftw commented Aug 22, 2025

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"

Summary by CodeRabbit

  • New Features

    • Added localization across the app with support for English, French, Spanish, and Portuguese.
    • Automatically detects device language with graceful fallback to English.
    • Localized key UI areas: navigation, authentication, chat, teams, billing, settings, errors/success messages, and common actions.
    • Improved startup to initialize translations before rendering for a smoother experience.
  • Documentation

    • Added a comprehensive localization guide covering setup, supported platforms, adding new languages, testing steps, troubleshooting, and future enhancements.

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"
@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

Introduces 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

Cohort / File(s) Summary
Docs: Localization Guide
frontend/README-localization.md
Adds a detailed i18n README covering locale detection, assets layout, setup steps, testing, troubleshooting, and future enhancements.
Locale Assets
frontend/public/locales/en.json, frontend/public/locales/es.json, frontend/public/locales/fr.json, frontend/public/locales/pt.json
Adds translation resources for English, Spanish, French, and Portuguese with structured UI keys and placeholders.
i18n Bootstrap and UI Integration
frontend/src/utils/i18n.ts, frontend/src/main.tsx, frontend/src/routes/index.tsx, frontend/src/components/TopNav.tsx
Introduces i18n initialization (resolve locale, load JSON, init i18next), defers render until init completes, and replaces hardcoded labels with t(...) in Index and TopNav.
Frontend Dependencies
frontend/package.json
Adds i18next (^23.6.0) and react-i18next (^13.0.0) to dependencies.
Tauri Native & Config
frontend/src-tauri/Cargo.toml, frontend/src-tauri/src/lib.rs, frontend/src-tauri/tauri.conf.json
Adds tauri-plugin-localization, initializes the plugin in the Tauri app, and allowlists protocol.asset in config.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Tauri #23 — Also updates Tauri native layer; relates to adding and wiring native plugins.
  • Ios #55 — Modifies Tauri plugins and config, overlapping with added localization plugin and allowlist changes.
  • Public Release #2 — Touches frontend/src/components/TopNav.tsx, same component updated here for i18n labels.

Poem

In burrows of strings I hop and align,
Keys turn to phrases—now parsed, by design.
From en to fr, es, pt I glide,
With tauri winds, I calmly ride.
Click, tap, render—no hard-coded fray,
A multilingual meadow for users today. 🐇✨

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch i18n

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@greptile-apps greptile-apps bot left a 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.tsx and ensure proper testing across different locale scenarios

14 files reviewed, 1 comment

Edit Code Review Bot Settings | Greptile

<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>
Copy link
Contributor

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

Suggested change
<NavLink to="/proof">Proof</NavLink>
<NavLink to="/proof">{t('navigation.proof')}</NavLink>

Copy link

@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 (19)
frontend/package.json (1)

20-21: i18n compatibility & configuration verified

  • Confirmed in frontend/package.json that 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.0

This 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 detected

Investigation 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 with t('…') 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.email vs. 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.ts comments 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 keys

Follow 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 accessibility

Alt 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 style

Import and use i18next’s Resource type for loadResources to 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 supportedLngs

Since 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 style

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

📥 Commits

Reviewing files that changed from the base of the PR and between dd650a2 and 2b1556e.

⛔ Files ignored due to path filters (2)
  • frontend/bun.lockb is excluded by !**/bun.lockb
  • frontend/src-tauri/gen/apple/maple_iOS/Info.plist is 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.tsx
  • frontend/src/main.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/utils/i18n.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript typing and avoid any when possible

Files:

  • frontend/src/routes/index.tsx
  • frontend/src/main.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/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 between en.json, es.json, fr.json, and pt.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 initialized

The startup sequence correctly waits on initI18n before rendering, avoiding flashes of untranslated content.

frontend/README-localization.md (1)

181-185: FOUC prevention verified

Confirmed 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. In frontend/src/utils/i18n.ts, the code correctly attempts the Tauri plugin then falls back to navigator.language without 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 plugin

Confirmed 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

Comment on lines +49 to +55
"tauri": {
"allowlist": {
"protocol": {
"asset": true
}
}
},
Copy link

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:


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.

Comment on lines +31 to +57
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> } };
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

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.

Suggested change
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;
}

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.

2 participants