Skip to content

Localization

Flying Pizza edited this page Apr 19, 2026 · 2 revisions

GrainLab supports 9 languages. This page explains how to add a new language or update existing translations.


How Locale Detection Works

On first load, src/i18n/index.ts :: getLocale() detects the language in this priority order:

  1. localStorage.getItem('lang') — previously saved user preference.
  2. navigator.language — the browser's reported language.
  3. Falls back to 'en' if no match is found.

The detected code must be one of the 9 supported LocaleCode values:

'en' | 'zh-CN' | 'zh-TW' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'pt'

The user's manual selection (via the top-right language picker) is saved to localStorage and takes priority on subsequent loads.


File Structure

Each language is a single TypeScript file in src/i18n/:

src/i18n/
├── index.ts     ← registers all locales, exports LOCALES list
├── en.ts        ← English (canonical shape — all other files must mirror this)
├── zh-CN.ts
├── zh-TW.ts
├── ja.ts
├── ko.ts
├── fr.ts
├── de.ts
├── es.ts
└── pt.ts

Each file exports a default object. The shape is inferred from en.ts and is used as the MessageSchema type throughout the app — TypeScript will report an error if a locale file is missing a key.


Translation Key Structure

// src/i18n/en.ts
export default {
  app: { ... },          // toolbar button labels
  canvas: { ... },       // canvas placeholder text
  preset: { ... },       // film preset names and descriptions
  control: { ... },      // effect parameter labels
  export: { ... },       // export dialog strings
  batch: { ... },        // batch panel strings
  gallery: { ... },      // gallery / film strip strings
  theme: { ... },        // dark/light mode toggle labels
  lang: { ... },         // language selector labels
}

Updating an Existing Translation

  1. Open the target locale file, e.g. src/i18n/fr.ts.
  2. Find the key you want to update and edit the string value.
  3. Run npm run dev to confirm there are no runtime warnings.
  4. Run npm run build to confirm there are no TypeScript type errors.

Adding a New Language

1. Create the locale file

Copy src/i18n/en.ts to a new file, e.g. src/i18n/it.ts, and translate every string value. Keep all key names unchanged — only the values should differ.

// src/i18n/it.ts
export default {
  app: {
    title: 'GrainLab',
    open: 'Apri',
    compare: 'Confronta',
    export: 'Esporta',
    batch: 'Batch',
  },
  // ... translate all remaining keys ...
}

2. Register the locale in src/i18n/index.ts

import it from './it'                              // ① import

export type LocaleCode = '...' | 'it'              // ② add to union type

export const LOCALES: { code: LocaleCode; label: string }[] = [
  // ... existing entries ...
  { code: 'it', label: 'Italiano' },               // ③ add to display list
]

// Inside getLocale():
if (nav.startsWith('it')) return 'it'              // ④ add auto-detection

// Inside createI18n messages:
messages: {
  // ... existing ...
  it,                                              // ⑤ register messages
}

3. Verify

npm run build

TypeScript will surface any missing translation keys. Fix them before submitting a PR.


Adding Translations for a New Preset

When a new film preset is added (see Adding a Film Preset), its i18nKey must be added to all locale files under the preset key:

// All 9 locale files
preset: {
  // ... existing presets ...
  myNewFilm: { name: 'My New Film', desc: 'Description in this language.' },
},

Tips

  • The en.ts file is the source of truth for the schema. If you add a new key to en.ts, add it to all other locale files as well.
  • Untranslated strings automatically fall back to the en value at runtime (configured via fallbackLocale: 'en' in createI18n), so missing keys won't crash the app — but they should be filled in before release.
  • Right-to-left (RTL) languages are not currently supported at the CSS level.

Clone this wiki locally