113 lines
15 KiB
Markdown
113 lines
15 KiB
Markdown
# 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.
|