Files
Cibello-app/docs/20-internationalisering.md
T
2026-08-05 19:21:11 +07:00

113 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Del 20 Internationalisering: audit, målarkitektur & migreringsplan
Styrande spec: den uppladdade i18n-specifikationen (`*_Internationalisering_Claude.md`). Detta dokument är
den audit + plan som i18n-spec §34 kräver före implementation, samt kvitto på vad som
redan är genomfört. Status: ✅ klart · 🔨 genomfört i denna leverans · 📋 planerat (fas angiven).
## 1. Nulägesrapport (audit av kodbasen)
### Redan korrekt (byggt språkneutralt från start)
| Område | i18n-spec | Läge |
| ------------------------------------------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Interna enums (måltid, kök, diet, plats, svårighet, notistyp, plan, creator-nivå …) | §4, §16 | ✅ Engelska maskinidentifierare överallt (`dinner`, `fridge`, `vegetarian`) |
| Canonical ingredient-IDs | §12 | ✅ Engelska slugs (`chicken_breast`); `aliases[]` finns; nameSv/nameEn-kolumner |
| Ingrediensdensitet | §10 | ✅ `density_g_per_ml` + `grams_per_piece` per ingrediens; generell volym↔massa-konvertering vägrar utan densitet (returnerar null) |
| Nutrition canonical per 100 g + provenance | §19 | ✅ (kJ + natrium/salt-profiler 📋 fas i7) |
| Allergener som canonical IDs, deterministiska | §18 | ✅ (marknadsregler/visningsprofiler 📋 fas i7) |
| Högtider datadrivna med market-kod + datumregler | §17 | ✅ `season_events(market, date_rule, lead_days …)` uppfyller market_events-syftet |
| Datum: timestamps UTC (`timestamptz`), bäst före som lokalt kalenderdatum (`date`) | §21 | ✅ |
| AI: canonical separerat från displaytext i kontrakten (`canonicalIngredientId` + `detectedName`) | §22, §30 | ✅ |
| Minne: strukturerad `value` + key, inte fritextsanning | §23 | ✅ delvis (summarySv finns som cache renderas om vid språkbyte 📋 fas i6) |
| Recept: struktur (ingrediens-IDs, mängder, tider, temperaturer, DNA) skild från text | §13 | ✅ struktur; översättningstabeller 📋 fas i5 |
| Varumärket som konfiguration | §28 | 🔨 `brand.config.json` enda platsen; kod svep-verifierad |
| Store-produkt-IDs språkneutrala | §25 | 🔨 `household_monthly` m.fl., härledda ur brand-config |
### Avvikelser som åtgärdas i DENNA leverans (🔨)
1. **Enhetskoder var svenska** (`msk`, `tsk`, `krm`, `dl`, `st`, `portion`) brott mot §11.
Åtgärd: språkneutrala koder `GRAM, KILOGRAM, MILLILITER, DECILITER, LITER, TEASPOON,
TABLESPOON, CUP_US, FLUID_OUNCE_US, OUNCE, POUND, COUNT, PORTION, PINCH, SLICE, CLOVE,
CAN, PACKAGE` i enum/DB/motorer/seed/API/app. Dokumenterad standard (§10):
TEASPOON=5 ml, TABLESPOON=15 ml (metrisk), CUP_US=236,59 ml, FL_OZ_US=29,57 ml,
OUNCE=28,35 g, POUND=453,59 g, PINCH≈0,5 ml. `krm` i svenska recept = 1 ml →
lagras som MILLILITER; svensk visning ("krm", "msk") sker via översättningslagret.
2. **UserLocalePreferences saknades** (§6): ny tabell `user_locale_preferences`
(languageTag BCP 47, regionCode ISO 3166-1, timeZone IANA, measurementSystem
METRIC/US_CUSTOMARY/MIXED, temperatureUnit, currencyCode ISO 4217, firstDayOfWeek,
use24HourTime) + `GET/PATCH /v1/me/locale-preferences`; språk ≠ region ≠ enheter.
3. **AAMOS-anrop saknade full lokaliseringskontext** (§22): kuvertets metadata har nu
`localeContext` (languageTag, regionCode, timeZone, measurementSystem,
temperatureUnit, currencyCode) och skickas från API/worker.
4. **Hårdkodade UI-texter i mobilappen utanför i18n-modulen** (§7): options-/etikett-
kartor (onboarding, samtycken, måltidstyper, butiksavdelningar, enhetsetiketter,
Alert-rubriker) flyttade till översättningsnycklar; `t()` injicerar `{brand}`.
5. **Notiser sparade endast färdig text** (§26): `notifications` har nu
`template_key`, `variables`, `locale`; renderad text behålls som cache och
renderas om vid utskick.
6. **Namnet i koden** (§28): all kod avvarumärkad paketscope `@app/*`, UI via
`{brand}`, tjänste-/könamn ur brand-slug, neutrala standardvärden i config.
### Status M1M10
| Fas | Status | Verifiering |
| --- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| M1 | ✅ Genomförd | Alla pengakolumner `*_minor` (integer) + `households.currency_code`; E2E: budget i minor + valuta, receptfilter på minor-kostnad |
| M2 | ✅ Genomförd | `ingredient_translations` (101 en-rader seedade ur nameEn) + `unit_translations` (36 rader sv+en) + `/v1/i18n/units`; en-sök träffar översättningar |
| M3 | ✅ Genomförd | `recipe_translations` + `recipe_step_translations`; TRANSLATE_RECIPE-jobb → deterministisk verifiering (stegantal, bevarade tal, konfidens) → admin publicerar; E2E: en-användare får översatt titel/steg/ingrediensnamn med sv-fallback |
| M4 | ✅ Genomförd | i18next med Intl-plural (`_one`/`_other`), locales/{sv,en}/common.json (175 nycklar, paritetstestad), språkbyte utan omstart (versionsbaserad re-mount), språkval i profilen synkat mot locale-preferenser |
| M5 | ✅ Genomförd | `displayQuantity` i shared-types (METRIC/US_CUSTOMARY/MIXED, kvartsavrundning för cups/tsp, ≈-märkning); mobilens formatQuantity konverterar per användarens måttsystem; 15 tester |
| M6 | ✅ Genomförd | `nutrition_display_profiles` + `allergen_market_rules` seedade (EU/SE/GB 14, US 9, CA 12) + `/v1/i18n/nutrition-profile` med EU-fallback; kJ- och natrium↔salt-hjälpare med tester |
| M7 | ✅ Genomförd | unaccent + pg_trgm + trigramindex (skapas idempotent i migrate, master-varianten i create-database.sql); sök: "creme"→Crème fraiche, "jordgubar"→Jordgubbar |
| M8 | 📋 Kvar (ops) | Store-metadata, skärmbilder och prissättning per marknad görs i butikskonsolerna inför lansering produkt-ID:n redan varumärkes- och språkneutrala |
| M9 | 📋 Kvar (beslut D-024) | Adminpanelen förblir svensk tills internationell ops finns |
| M10 | ✅ Genomförd | `buildMemoryOverview(items, languageTag)`: rubriker per språk, deterministisk summary-rendering ur strukturerad value med sv-fallback; nya minnen får lokaliserad summary via localeContext |
### Ursprunglig plan (behålls som referens)
| # | Åtgärd | i18n-spec | Fas | Kommentar |
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| M1 | Pengar i minor units + valuta: alla `*_sek`-kolumner → `*_minor int` + `currency_code` (hushållsnivå), `Money`-typ i API | §20 | i5 | Typ + hjälpare levererade i `shared-types/money.ts`; kolumnbyte görs som nästa migration MEDAN databasen är produktionstom ingen datamigrering behövs |
| M2 | `ingredient_translations` + flytt av nameSv/nameEn → rader; `unit_translations` i DB (visning finns nu i app-lagret) | §1112 | i3 | Canonical-IDs är redan rätt → ren additiv migration |
| M3 | `recipe_translations` + `recipe_step_translations`; AI-utkast med status DRAFT_AI→…→PUBLISHED + automatiska verifieringar (samma IDs/mängder/tider/allergener) | §1314 | i5 | Receptstrukturen är redan språkoberoende |
| M4 | i18next + ICU-plural i mobilen (ersätter interims-t()), namespaces `/locales/{lng}/{ns}.json`, expo-localization för förslag, språkbyte utan omstart, CI-vakt mot hårdkodad text | §5, §78, §31 | i4 | Nyckelstrukturen är redan namespace-formad (`home.*`, `scan.*` …) → mekanisk flytt. Motivering i beslutslogg D-023 |
| M5 | Måttvisningstjänst: canonical→display per measurementSystem (ml→cup/tsp, g→oz/lb, °C→°F) med förlustfri lagring (canonical + original + källa + confidence) | §910, §30 | i5 | Konverteringstabellen finns; visningsvalet styrs av locale-prefs |
| M6 | `nutrition_display_profiles`, `allergen_market_rules`, kJ + natrium | §1819 | i7 | |
| M7 | Sök: unaccent + pg_trgm + alias/synonymtabeller per locale | §24 | i5 | `ingredient_aliases`-fältet finns redan |
| M8 | Lokaliserad store-metadata, screenshots, prissättning per marknad | §25 | i8 | Produkt-IDs redan neutrala |
| M9 | Adminpanelen till nycklar | §7 | i8 | Beslut: internt verktyg får vara svenskt tills internationell ops finns (D-024) |
| M10 | AAMOS-minne: rendera summaries ur strukturerad value vid läsning i valt språk | §23 | i6 | value är redan strukturerad |
## 2. Berörda filer (denna leverans)
`packages/shared-types` (enums/units/brand/money/locale) · `packages/validation`
(unit-schema, locale-prefs-schema) · `packages/database` (unit-enum, user_locale_preferences,
notifications-kolumner, seed) · motorerna (enhetsberäkning via kodtabellen) ·
`packages/ai-contracts` (localeContext) · `apps/api` (locale-prefs-routes, kontext till
AAMOS, neutrala defaults) · `apps/worker` (notismallar, kontext) · `apps/mobile`
(unit-etiketter, nya nycklar, brand-injektion) · alla package.json (scope `@app/*`).
## 3. Migreringsplan & rollback
Pre-launch utan produktionsdata ⇒ enum-/kolumnbyten görs i **basmigrationen**
(0000 regenererad; loggat i Del 7). Efter lansering gäller i stället spec §29-mönstret:
nya kolumner → mapping → dubbelskrivning → verifiering → borttag efter kontroll; varje
migration med backup (deploy.sh vägrar utan dump) och nedskriven rollback.
Rollback för denna leverans: git-tag före sveper + regenererbar databas.
## 4. Testplan (§31)
🔨 Nu: enhetstester för alla nya enhetskoder inkl. cup/oz/lb-konvertering och
"vägra-utan-densitet"; locale-prefs-API; oförändrade motorresultat (61 befintliga tester
gröna efter kodbytet). 📋 i4i8: pseudolokal-test (nycklar aldrig synliga), plural-ICU,
formatteringstester (decimal/tusental/datum/valuta) för sv-SE, en-US, en-GB, de-DE,
fi-FI, snapshot av receptöversättningsverifieringen, CI-grep-vakt mot hårdkodade strängar.
## 5. Exit criteria-läge (§33)
Uppfyllda redan: språkneutrala enums, canonical ingredienser, datadrivna högtider,
UTC/lokaldatum, AAMOS-kontext, enhetsbyte utan dataförlust (canonical lagring),
nytt språk = nya rader (ingen schemaändring), nytt land = data (market-rader).
Återstår före internationell lansering: M1M8 ovan uppskattat 23 sprintar, kan ske
parallellt med svensk beta eftersom allt är additivt.