# 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 M1–M10

| 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)                                                             | §11–12        | 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)                   | §13–14        | 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, §7–8, §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)                      | §9–10, §30    | i5  | Konverteringstabellen finns; visningsvalet styrs av locale-prefs                                                                                        |
| M6  | `nutrition_display_profiles`, `allergen_market_rules`, kJ + natrium                                                                                                              | §18–19        | 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). 📋 i4–i8: 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: M1–M8 ovan – uppskattat 2–3 sprintar, kan ske
parallellt med svensk beta eftersom allt är additivt.
