Skip to content

feat: lightweight i18n for sidebar navigation (Part 2 of #382)#1169

Open
IgnazioDS wants to merge 3 commits into
Kiln-AI:mainfrom
IgnazioDS:feat/lightweight-i18n
Open

feat: lightweight i18n for sidebar navigation (Part 2 of #382)#1169
IgnazioDS wants to merge 3 commits into
Kiln-AI:mainfrom
IgnazioDS:feat/lightweight-i18n

Conversation

@IgnazioDS
Copy link
Copy Markdown

@IgnazioDS IgnazioDS commented Mar 26, 2026

Summary

Implements Part 2 of #382 — a lightweight translation system for key UI strings that browser translation extensions handle poorly.

This is intentionally minimal, per the issue requirements:

  • No heavy i18n library — just a Svelte store + plain TypeScript objects
  • Only translates sidebar navigation labels and common actions
  • Leaves 99% of the app for browser extensions to translate
  • Auto-detects browser language, persists choice in localStorage
  • Falls back to English for any missing translations

Languages Supported

English, Spanish (Español), Chinese (中文), Japanese (日本語), Korean (한국어), French (Français), German (Deutsch), Portuguese (Português)

How It Works

<script>
  import { t } from "$lib/i18n"
</script>

<!-- Before: -->
<a href="/">Run</a>

<!-- After: -->
<a href="/">{$t('nav.run')}</a>

Adding new translations is simple — add a key to translations.ts and use $t('key') in the template. Contributors can fix bad translations by adding/updating entries in the translations object.

Files

File Purpose
lib/i18n/translations.ts Translation strings for 8 languages + lookup function
lib/i18n/index.ts Reactive locale store + derived $t helper
routes/(app)/+layout.svelte Sidebar nav strings now use $t()

What's NOT Included (by design)

  • No language picker UI (can be added later in Settings)
  • No translation of data/LLM outputs (correctly left for translate="no" from Part 1)
  • No translation of every string — just the strategic ones that browser extensions struggle with

Test Plan

  • App renders correctly in English (default)
  • Set kiln_locale to "zh" in localStorage → sidebar shows Chinese
  • Missing translation keys fall back to English
  • No visual regression in sidebar layout

Addresses Part 2 of #382

Summary by CodeRabbit

  • New Features
    • Added multi-language support (English, Spanish, Chinese, Japanese, Korean, French, German, Portuguese).
    • UI navigation labels now display localized text.
    • App auto-detects browser language on first visit and falls back to English when needed.
    • Language preference is saved to persist across sessions.

Add a minimal translation system for strategically translating key UI
strings that browser translation extensions handle poorly. This is NOT
a full i18n framework — it covers sidebar navigation labels and common
actions, leaving 99% of the app for browser extensions to translate.

Implementation:
- Svelte store-based locale with localStorage persistence
- Auto-detects browser language on first load
- Falls back to English for missing translations
- 8 languages: English, Spanish, Chinese, Japanese, Korean, French,
  German, Portuguese

Files:
- lib/i18n/translations.ts — translation strings and lookup function
- lib/i18n/index.ts — reactive locale store and $t helper
- routes/(app)/+layout.svelte — sidebar strings use $t()

Addresses Part 2 of Kiln-AI#382
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f20ff8c7-393d-4878-9af0-c5fa07790a3e

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc6b58 and 6741782.

📒 Files selected for processing (1)
  • app/web_ui/src/lib/i18n/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/web_ui/src/lib/i18n/index.ts

Walkthrough

Adds a new i18n subsystem: translations and SupportedLocale type, a persisted Svelte locale store (localStorage key kiln_locale) with validation and browser auto-detect fallback, a derived t store returning a translation function, and replaces hardcoded navigation labels with localized lookups in the app layout.

Changes

Cohort / File(s) Summary
i18n Core
app/web_ui/src/lib/i18n/translations.ts, app/web_ui/src/lib/i18n/index.ts
New SupportedLocale type and localeNames; translations dictionary and translate(); exported persisted locale store backed by localStorage (kiln_locale) with sanitization/auto-detect, and derived t store producing (key: string) => string.
Layout Integration
app/web_ui/src/routes/(app)/+layout.svelte
Replaced hardcoded sidebar/navigation labels with localized lookups via the t derived store (template text nodes now use translation keys).

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant Storage as localStorage
    participant I18n as i18n module
    participant Layout as +layout.svelte

    Browser->>Storage: read "kiln_locale"
    alt stored and valid
        Storage-->>I18n: return persisted locale
    else missing or invalid
        Browser->>I18n: provide navigator.language
        I18n-->>I18n: sanitizePersistedLocale -> choose supported or "en"
        I18n->>Storage: clear invalid / write chosen locale
    end
    I18n-->>Browser: expose `locale` store and derived `t` (function)
    Layout->>I18n: subscribe to `t`
    I18n-->>Layout: emit translation function -> Layout renders localized labels
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through keys and nested strings,
I stored a tongue where small bell rings,
Menus now speak in many ways,
I nibble bytes and sing their praise,
Hello, hola, 你好 — I play! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing lightweight i18n for sidebar navigation as Part 2 of issue #382, which is confirmed by the PR description and code changes.
Description check ✅ Passed The description comprehensively covers what the PR does, related issues, languages supported, implementation details, and includes a test plan. The Contributor License Agreement section is omitted but the checklist is present and contextually acknowledged.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a lightweight internationalization (i18n) system to enhance the user experience by translating critical UI elements, such as sidebar navigation labels and common actions. It focuses on strategic translation where browser-based solutions are insufficient, while intentionally leaving the majority of the application for browser extensions to handle. The system automatically detects the user's preferred language and persists this choice, ensuring a more localized interface for supported languages.

Highlights

  • Lightweight Internationalization (i18n) System: Implemented a lightweight translation system for key UI strings, specifically targeting areas like sidebar navigation where browser extensions perform poorly.
  • Minimalist Approach: Introduced a minimalist approach using a Svelte store and plain TypeScript objects, intentionally avoiding heavy i18n libraries to keep the system lean.
  • Language Detection and Persistence: Enabled auto-detection of the browser's language on first load and persistence of the user's language choice in localStorage for a consistent experience.
  • Multi-language Support: Provided translations for 8 languages: English, Spanish, Chinese, Japanese, Korean, French, German, and Portuguese.
  • English Fallback: Ensured a robust fallback mechanism to English for any missing translation keys, preventing untranslated UI elements.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new internationalization (i18n) system for the web UI. It includes a Svelte store for managing user locale, persisting it in local storage, and auto-detecting browser language. A translation utility is added with support for multiple languages (English, Spanish, Chinese, Japanese, Korean, French, German, Portuguese), and the main layout's navigation labels have been updated to use this new translation function. A review comment points out that the supported locales array is hardcoded, which duplicates information and could lead to inconsistencies, suggesting it should be derived from the localeNames object.

Comment thread app/web_ui/src/lib/i18n/index.ts Outdated
} else {
// Auto-detect from browser language
const browserLang = navigator.language.split("-")[0]
const supported: SupportedLocale[] = ["en", "es", "zh", "ja", "ko", "fr", "de", "pt"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The supported array is hardcoded here. This duplicates the list of supported locales already defined in translations.ts (as part of SupportedLocale type and localeNames object). Duplication can lead to inconsistencies if a new language is added or removed in one place but not the other. It's better to derive this list from a single source of truth.

Suggested change
const supported: SupportedLocale[] = ["en", "es", "zh", "ja", "ko", "fr", "de", "pt"]
const supported: SupportedLocale[] = Object.keys(localeNames) as SupportedLocale[]

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/web_ui/src/lib/i18n/index.ts`:
- Around line 14-23: The persisted locale read via
localStorage.getItem("kiln_locale") is parsed unguarded and cast to
SupportedLocale causing crashes if malformed or unsupported; wrap JSON.parse in
a try/catch, only accept the parsed value for assignment to initialLocale if it
is a string and included in the supported locales list (the same list used
later), otherwise ignore the stored value and proceed to the browserLang/default
fallback; ensure the code only asserts to SupportedLocale after this validation
and does not let a malformed value reach initialLocale or cause an exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 07a0c2b8-2228-43d2-92f6-d8ad0b6aa8e9

📥 Commits

Reviewing files that changed from the base of the PR and between c46041a and 241d5d8.

📒 Files selected for processing (3)
  • app/web_ui/src/lib/i18n/index.ts
  • app/web_ui/src/lib/i18n/translations.ts
  • app/web_ui/src/routes/(app)/+layout.svelte

Comment thread app/web_ui/src/lib/i18n/index.ts Outdated
- Wrap JSON.parse in try/catch to prevent crashes from malformed
  localStorage data
- Derive supported locales from localeNames (single source of truth)
  instead of hardcoding the list
- Add isSupportedLocale type guard for safe validation

Addresses review feedback from gemini-code-assist and coderabbitai
@IgnazioDS
Copy link
Copy Markdown
Author

Addressed both review comments:

  1. @gemini-code-assist — Supported locales are now derived from localeNames via Object.keys() instead of being hardcoded. Single source of truth.
  2. @coderabbitaiJSON.parse is now wrapped in try/catch with a proper type guard (isSupportedLocale) that validates before assignment. Malformed or unsupported values fall through to browser detection or the English default.

Thanks for the feedback!

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Thank you for the update, IgnazioDS. Using Object.keys() to derive supported locales and implementing robust error handling for localStorage are excellent improvements that enhance the maintainability and reliability of the i18n system. These changes look great.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

Tip

For best results, initiate chat on the files or code changes.

@IgnazioDS Great work addressing both points! The isSupportedLocale type guard combined with the try/catch around JSON.parse is exactly the right pattern — it keeps the locale initialization safe against tampered or stale localStorage values while keeping the fallback chain clean. And deriving the supported locale list from localeNames via Object.keys() is a solid single-source-of-truth approach. 🎉

(´・ω・`) ∫ ∫
/づづ 🐇✨


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/web_ui/src/lib/i18n/index.ts`:
- Around line 16-43: The validation you added in createLocaleStore is bypassed
because localStorageStore re-reads the key itself and will return the raw
(possibly invalid) value or throw on malformed JSON; before calling
localStorageStore("kiln_locale", initialLocale) ensure the stored value is safe
by reading localStorage.getItem("kiln_locale") inside a try/catch, JSON.parse it
safely, and if it is malformed or not isSupportedLocale remove the key
(localStorage.removeItem("kiln_locale")) so localStorageStore will initialize
with the validated initialLocale; reference createLocaleStore,
localStorageStore, and the "kiln_locale" key when making this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cb43c102-7392-4faf-824b-e924a66b5252

📥 Commits

Reviewing files that changed from the base of the PR and between 241d5d8 and 6cc6b58.

📒 Files selected for processing (1)
  • app/web_ui/src/lib/i18n/index.ts

Comment thread app/web_ui/src/lib/i18n/index.ts Outdated
@IgnazioDS
Copy link
Copy Markdown
Author

@scosman Hi! This addresses Part 2 of #382 with a lightweight approach — Svelte store + plain TS objects, no heavy i18n library. I've addressed all automated review feedback (guarded JSON.parse, single source of truth for supported locales).

Ready for human review when you have a moment. Happy to adjust the approach if you'd prefer a different pattern!

localStorageStore independently calls JSON.parse on the raw stored
value, bypassing any validation done beforehand. Fix by sanitizing
(and removing invalid entries) from localStorage before constructing
the store, so localStorageStore always reads a valid locale or falls
back to the provided default.

Addresses CodeRabbit review feedback on Kiln-AI#1169
@IgnazioDS
Copy link
Copy Markdown
Author

Pushed a fix addressing the critical CodeRabbit finding — localStorageStore independently reads from localStorage, bypassing our validation.

The fix sanitizes (and removes invalid entries from) localStorage before constructing the store, so localStorageStore always reads a valid locale or falls back to the provided default. This prevents crashes from malformed or unsupported persisted values.

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.

1 participant