import InfoBox from '../components/InfoBox.astro';
import PullQuote from '../components/PullQuote.astro';

When Norwegian language support returned during [norwegian-done-properly](/posts/norwegian-done-properly), UI copy was split across three separate files: a TypeScript interface defining 396 string keys, an English dictionary file, and a Norwegian dictionary file. Adding or updating a UI string meant editing three places in parallel. While TypeScript guaranteed that every key declared in the interface existed in both dictionaries, nothing checked that the Norwegian translation actually differed from the English source copy.

## Co-locating translations per key

To eliminate three-file edit friction, all 396 string definitions were collapsed into a single `strings.ts` module. Each entry pairs English and Norwegian copy directly inside a per-key object literal, placing both language variants side by side.

```typescript
export const strings = defineStrings({
  backspace: {
    en: 'Backspace',
    no: 'Slett'
  },
  submitWord: {
    en: 'Submit word',
    no: 'Send inn ord'
  }
});
```

Co-locating translations ensures that any engineer or automated agent adding a UI string sees both language requirements in the same line of code.

<InfoBox variant="note" title="Preserving export stability">
The public UI string module maintained its existing helper functions and interface definitions unchanged, allowing 17 consumer components to switch module locations without behavioral edits.
</InfoBox>

## Enforcing translation completeness at compile time

Structural typing alone can let a missing or mistranslated string pass if the property types overlap. To turn translation parity into a hard compile constraint, `defineStrings` leverages TypeScript's `NoInfer<T[K]>` utility on the Norwegian translation slot.

<PullQuote attribution="Type-safe localization">
Using NoInfer on translation inputs forces TypeScript to evaluate Norwegian copy against English parameters directly at compile time.
</PullQuote>

If a function-valued UI string accepts parameters in English, `NoInfer` prevents TypeScript from inferring loose parameter types in Norwegian, making any parameter mismatch a build error before unit tests even run.

## Guarding against copy-paste regression with tests

Alongside compile-time type checks, a new unit test in `ui.test.ts` scans all 396 dictionary entries for byte-identical translations. While certain technical terms (such as `PvP` or `iOS`) legitimately share spelling across languages, un-translated strings now trigger test failures unless explicitly registered on a documented allowlist.

```typescript
it('flags un-translated strings outside documented allowlist', () => {
  for (const [key, { en, no }] of Object.entries(rawStrings)) {
    if (en === no && !ALLOWED_IDENTICAL_STRINGS.has(key)) {
      throw new Error(`Un-translated string key: ${key}`);
    }
  }
});
```

Migrating the 396 keys was executed using an automated AST transformation. An automated code-review pass caught that 32 per-key JSDoc comments were omitted in the initial transformation pass, prompting a fix before the refactor landed on `main`.