# App localization — Functional Specification

**Status:** Approved v1.1 · 2026-07-21
**Scope of this document:** User-visible language selection and localization behavior for the web, desktop, and Android apps. Technical design and implementation follow only after this specification is approved.

---

## 1. Purpose

The app must be usable in **Latvian, Russian, and English**. It chooses a useful language on first use, lets the user change that language at any time, and remembers the user's explicit choice on that device.

Localization applies to the complete app-owned experience, not only the main search form. Search history, store progress, errors, diagnostics UI, result controls, human-check guidance, `Pirkumu plāns`, confirmations, warnings, dates, counts, prices, accessibility labels, and page metadata must remain in the same selected language.

## 2. Supported languages

| Language | Code | Formatting locale | Selector name |
|---|---|---|---|
| Latvian | `lv` | `lv-LV` | `Latviešu` |
| Russian | `ru` | `ru-RU` | `Русский` |
| English | `en` | `en-GB` | `English` |

- Latvian remains a fully supported language; Russian and English are added alongside it.
- Language names in the selector are written in their own language so the control remains understandable even when the current language was selected accidentally.
- Flags are not used to represent languages.

## 3. Initial language selection

On startup, the app resolves its language in this order:

1. A previously saved, supported language explicitly selected by the user.
2. On a device with no saved choice, the first supported language in the device's ordered language preferences. Region variants use their base language, so `ru-LV` selects Russian and `en-US` selects English.
3. Latvian when none of the device/browser preferences match `lv`, `ru`, or `en`, or when the language information is unavailable.

The detected language is an initial default, not a permanent restriction. A saved explicit choice always wins over later device-language changes. Missing, malformed, or obsolete saved values are ignored safely and the normal initial-selection rules are applied.

Existing users who have no saved language choice receive the same first-run resolution when this feature is introduced.

- Android reads the locale list from the native Android `Configuration`; it does not treat the WebView's `navigator.language` as authoritative.
- Web and desktop use the browser/Chromium ordered language list.
- Android locale resolution completes before the first app UI is rendered, so the selected language and visible copy agree on first paint.

## 4. Language selector

- The language selector lives in the Settings surface defined by `SETTINGS-FUNCTIONAL-SPEC.md`; it is not shown directly in the app header.
- The selector uses the generated Spartan NG Select component and app design tokens. It must not be a native HTML `<select>` or invoke a browser/operating-system selector sheet.
- Opening it presents all three languages using the names in section 2 and clearly marks the active choice.
- The complete control is usable by touch, pointer, and keyboard and exposes its state to assistive technology.
- Selecting a language applies it immediately. A reload, restart, confirmation, or navigation to a settings page is not required.
- Changing language does not clear or restart the current search, discard results, close the current workflow, change filters, alter search history, or modify `Pirkumu plāns`.
- The selector overlay remains reachable when a translated string is longer than its Latvian equivalent and must not be clipped by the Settings surface or supported viewport sizes.

## 5. Remembering the choice

- An explicit selection is saved immediately on the current device or browser profile.
- The choice survives page reloads, browser restarts, desktop app restarts, Android app restarts, and ordinary app updates.
- The preference is device-local and is not synchronized through the shared comparator backend. This allows, for example, one shared deployment to be used in Russian on one device and Latvian on another.
- Web storage is scoped to the app origin. Desktop uses its persistent Chromium profile. Android stores the explicit choice in Capacitor Preferences rather than relying on WebView local storage.
- On the first revised build, a valid explicit choice written by the earlier implementation to WebView local storage is migrated into Android Preferences before normal resolution. An absent value is not treated as an explicit Latvian choice.
- If preference storage is temporarily unavailable, the selected language still applies for the current session. Failure to save a language must not block searching or any other app behavior.
- Removing browser/app data or uninstalling the app may remove the saved choice; the next startup then uses the initial-selection rules in section 3.

## 6. What is translated

All first-party, user-facing text is localized, including:

- app title, tagline, search field, buttons, filters, column labels, empty states, loading states, warnings, and error explanations;
- search-history labels, relative and exact timestamps, removal labels, and confirmations;
- store-progress summaries and actions, including correct count grammar;
- diagnostics dialog labels, outcome descriptions, copy feedback, and unavailable-value labels;
- human-check instructions and app-owned controls around embedded or separate solve windows;
- `Pirkumu plāns`, including both views, sorting, quantities, refresh states, stale/change warnings, alternative-selection flow, and confirmations;
- tooltips, accessible names, screen-reader-only text, document title, and the document language metadata.

User-visible app text must come from the localization catalogue rather than remain as Latvian literals in templates or component logic. Translation changes must not alter machine-readable store IDs, error codes, storage values, or diagnostic field keys used for support.

## 7. Content that is not translated

The app preserves source-faithful content received from stores:

- store names;
- brand names and part numbers;
- product titles, attributes, availability wording, and delivery notes supplied by a store;
- external store pages and their human-verification challenges;
- user-entered part numbers;
- copied technical diagnostic report field names and machine-readable values, which remain stable for support tooling.

The app does not machine-translate store content or imply that a translated interpretation is the store's own wording. App-owned labels surrounding that content are translated.

## 8. Locale-aware formatting

- Dates and times use the selected language's formatting locale and translated relative labels such as `today` and `yesterday`.
- Prices remain EUR and use the selected locale's decimal, grouping, currency, and spacing conventions.
- Counts and sentences use the selected language's plural rules, including Russian plural forms; they are not assembled with Latvian-only singular/plural helpers.
- Sorting of app-owned localized labels is locale-aware. Store and product data retain their existing functional ordering unless a feature explicitly sorts visible names alphabetically.
- Part numbers, canonicalization, query matching, URLs, stored timestamps, and numeric calculations are language-independent.

## 9. Switching behavior and consistency

- Every visible app-owned surface updates to one language in the same render cycle, including an already-open panel, sheet, or dialog.
- Newly opened native confirmations and app-owned notifications use the current language.
- The root document `lang` value and localized document title update with the selection.
- Right-to-left layout is out of scope because all three supported languages are left-to-right.
- A missing translation must be caught during development. Production must never display a raw translation key or silently mix Latvian, Russian, and English app-owned strings.

## 10. Accessibility

- The selector has a visible active value and a localized accessible name; understanding it does not depend on colour alone.
- Keyboard focus remains on the Spartan NG selector trigger after a language is chosen and its overlay closes predictably.
- Screen-reader labels, status announcements, expanded/collapsed state descriptions, and confirmation text change with the selected language.
- Changing the language does not unexpectedly move focus or announce unrelated content as new.
- The document language metadata always matches the active language so assistive technology uses the appropriate pronunciation rules.

## 11. Failure and fallback behavior

- Failure to read the stored preference falls back to device-language detection and then Latvian.
- Failure to save a new preference does not undo the current-session language or block the app.
- An incomplete or invalid catalogue is a build/test failure. It is not handled by showing untranslated keys to users.
- Unknown backend or store error details may be shown as sanitized source text only where the current diagnostics behavior already permits it; the surrounding explanation remains localized.

## 12. Explicit non-goals

The first version does not include:

1. Automatic translation of store-supplied product or availability content.
2. Per-store language switching or changing the language sent to Latvian store adapters.
3. Synchronizing language choice between devices or through the comparator backend.
4. Choosing language from geographic location, IP address, SIM country, or store locale.
5. Additional languages beyond Latvian, Russian, and English.
6. User-editable translations or downloading language packs.
7. Right-to-left layout support.
8. Translating the stable installed product/brand name shown by the operating system.

## 13. Acceptance criteria

The feature is functionally complete when:

1. Every app-owned user-facing surface is available in Latvian, Russian, and English on web, desktop, and Android.
2. With no saved choice, `lv-*`, `ru-*`, and `en-*` device preferences select the matching language; unsupported preferences fall back to Latvian.
3. A saved explicit choice overrides device preferences and survives reloads, restarts, and ordinary app updates on each host.
4. Changing the selector updates the complete visible UI immediately without losing search input, results, filters, history, diagnostics, or purchase-plan state.
5. The Spartan NG selector is usable and its overlay is not clipped on supported desktop and mobile layouts in all three languages.
6. Dates, times, prices, counts, and plural forms follow the active locale, including Russian plural cases.
7. The document title, document `lang`, accessible names, confirmations, warnings, and error states use the active language.
8. Store-supplied names, titles, attributes, and availability text remain unchanged and clearly coexist with localized app chrome.
9. Missing catalogue entries fail automated checks, and production shows neither raw localization keys nor mixed-language app-owned text.
10. Preference read/write failure never blocks searching or other core behavior.

## 14. Proposed approval decisions

1. The supported set is Latvian, Russian, and English; Latvian remains the fallback.
2. Device/browser language is consulted only when there is no valid saved explicit choice.
3. The language choice is device-local rather than shared by the single-user backend.
4. The selector lives in Settings, uses Spartan NG, and uses language names rather than flags.
5. Language changes apply immediately without resetting the user's current workflow.
6. App-owned UI is translated completely; store-supplied content remains source-faithful.
7. Locale-native formatting is used, with English mapped to `en-GB` and EUR retained in every language.
