Skip to content

i18n Implementation

KanjiIQ uses Flutter's built-in internationalization framework with ARB (Application Resource Bundle) files for UI localization and PostgreSQL JSONB for content localization.

UI Localization with ARB

Configuration

The localization system is configured in frontend/l10n.yaml:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

ARB File Structure

Each language has an ARB file following ICU message format:

// lib/l10n/app_en.arb (template)
{
  "@@locale": "en",
  "appTitle": "KanjiIQ",
  "studyButton": "Start Study",
  "selectLanguage": "Select Language",
  "selectLevel": "Select JLPT Level",
  "cardCount": "{count} cards",
  "@cardCount": {
    "placeholders": {
      "count": { "type": "int" }
    }
  },
  "noConnection": "No internet connection. Using cached data.",
  "showAllLanguages": "Show all 51 languages"
}

Code Generation

Running flutter gen-l10n generates:

  • app_localizations.dart — Abstract base class with all localized strings
  • app_localizations_en.dart — English implementation
  • app_localizations_ja.dart — Japanese implementation
  • ... (51 generated files)

Usage in Flutter widgets:

// Access localized strings
Text(AppLocalizations.of(context)!.studyButton)

// With parameters
Text(AppLocalizations.of(context)!.cardCount(10))

Supported Locales Registration

Locales are registered in MaterialApp:

MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  locale: selectedLocale,  // From LocalePreferences
)

Content Localization

Flashcard content (kanji meanings, vocabulary definitions) is stored as JSONB in PostgreSQL and served via the API:

graph LR
    A[API Request<br/>?lang=pt] --> B[Backend]
    B --> C[PostgreSQL<br/>JSONB lookup]
    C --> D[Return meaning<br/>in Portuguese]

Translation Pipeline

New content is translated through the OpenAI API:

  1. Source content is authored in English
  2. Backend sends translation requests to OpenAI with context about JLPT level and usage
  3. Translations are stored in the JSONB column alongside existing translations
  4. Each language can be independently updated without affecting others

Self-hosted translation

The Kubernetes manifests include a LibreTranslate deployment (k8s/04-libretranslate-deployment.yaml) as an alternative to OpenAI. This provides fully self-hosted translation without external API dependencies, though quality varies by language pair.

UI vs Content Language

KanjiIQ allows the UI language and study language to differ:

  • UI language: The language of buttons, labels, and navigation (e.g., French)
  • Study language: The language for flashcard meanings (e.g., Portuguese)

A French-speaking user studying Japanese can have the interface in French while seeing flashcard meanings in Portuguese if they prefer.

Adding a New Language

To add a new (52nd) language:

  1. UI: Create lib/l10n/app_XX.arb with all translated strings
  2. Content: Add translations to JSONB columns via the translation pipeline
  3. Locale config: Add the language code to relevant locale_configs entries via the admin API
  4. Build: Run flutter gen-l10n to regenerate localization files

No schema migrations or backend code changes are required — the JSONB storage and ARB system are both designed for extensibility.