feat(i18n): översätt kvarvarande vyer + härda vakten (audit-backlog)

- API: översatt title/displayName/toName läggs nu bredvid Sv-fälten i favoriter,
  receptvarianter, /scaled, skapar-profiler, topplistor, memory-impact,
  substitutions — samt de denormaliserade titlarna (min dag, historik, matlådor,
  matlåde-förslag, veckoplan) via nullable recipeId med svensk fallback.
  10 endpoints, batchade resolvers (ett anrop per endpoint).
- Mobil: konsumerar de nya fälten med ?? Sv-fallback (swap-meal, sparade recept,
  varianter, min dag, logg, matlådor, veckoplan, inköpslista). Hårdkodade
  strängar -> t() (kitchen, scan-review, register, recept protein/Skapat av);
  decimalkomma -> Intl.NumberFormat. Allergen-etiketter -> t() (+5 nya nycklar).
  11 nya nycklar i alla 12 språk.
- i18n-vakt härdad: fångar nu hårdkodad svenska i prop={`...`}-mallliteraler
  (blind fläck förr). Verifierat att den fäller men inte ger falska positiv.
- whySv var redan lokaliserad (buildWhy med språktagg) - orörd.
- typecheck grönt (alla paket), vakt grön (603 nycklar).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 20:07:00 +00:00
parent f71b717284
commit 18388bb31c
31 changed files with 338 additions and 66 deletions
+16 -2
View File
@@ -106,6 +106,10 @@ const POS =
/(?:label|placeholder|title|text|header|message)\s*[=:]\s*(["'`])((?:(?!\1).)*[A-Za-zÅÄÖåäö]{2,}(?:(?!\1).)*)\1/g;
const ALERT =
/(?:Alert\.alert|setError|EmptyState\s+text=)\s*\(?\s*(["'`])((?:(?!\1).)*[A-Za-zÅÄÖåäö]{2,}(?:(?!\1).)*)\1/g;
// prop={`…`} mall-literal i JSX-UTTRYCK. POS ser inte {…}, så no-diakritisk
// svenska (»Skapat av ${x}«) slank förr igenom. Granska den literala resten.
const PROP_EXPR =
/(?:label|placeholder|title|text|header|message)\s*=\s*\{\s*`((?:[^`\\]|\\.)*)`\s*\}/g;
// JSX-text: >Svensk text< (fångar diakriter i textnoder)
const JSXTEXT = />\s*([^<>{}\n]*[åäöÅÄÖ][^<>{}\n]*?)\s*</g;
@@ -132,10 +136,20 @@ for (const file of files) {
let mm;
while ((mm = re.exec(line))) {
const val = mm[2];
if (val.includes("${")) continue; // ren interpolationsdel
if (!allowed(val)) hits.add(val);
// Mallsträngar granskas nu på den LITERALA resten (blind fläck förr
// `label={\`Skapat av ${namn}\`}` slank igenom hela). Strippa ${…} och
// se om ord blir kvar; bara emoji/tal/enheter kvar = ok.
const bare = val.replace(/\$\{[^}]*\}/g, " ");
if (!/[A-Za-zÅÄÖåäö]{2,}/.test(bare)) continue;
if (!allowed(bare)) hits.add(val);
}
}
PROP_EXPR.lastIndex = 0;
let pm;
while ((pm = PROP_EXPR.exec(line))) {
const bare = pm[1].replace(/\$\{[^}]*\}/g, " ");
if (/[A-Za-zÅÄÖåäö]{2,}/.test(bare) && !allowed(bare)) hits.add(pm[1]);
}
JSXTEXT.lastIndex = 0;
let jm;
while ((jm = JSXTEXT.exec(line))) {