Skip to content

Silent fallback to German when lang is set without prior registerTranslation #64

Description

@DaSchTour

Summary

When a consumer sets <keksmeister-banner lang="pl"> (or passes lang: 'pl' via config), the banner renders in German instead of Polish — even though the translation file keksmeister/i18n/pl ships in the same package. The fallback is completely silent: no console warning, no thrown error, the banner just looks German.

Same applies to every locale shipped in dist/i18n/* other than de and en: fr, es, it, nl, pl.

Repro

import { resolveTranslations } from 'keksmeister';
console.log(resolveTranslations('pl').banner.title);
// → "Cookie-Einstellungen"  (German — expected: "Ustawienia plików cookie")

Same shape in the browser:

<keksmeister-banner lang="pl" privacy-url="/privacy"></keksmeister-banner>

Renders the German strings.

Root cause

src/i18n/index.ts:

const builtinTranslations = { de, en };
const customTranslations = {};

export function resolveTranslations(lang) {
  if (!lang) return de;
  if (typeof lang === 'object') return lang;
  return customTranslations[lang] ?? builtinTranslations[lang] ?? de;
}

builtinTranslations is hardcoded to { de, en }. The pl.ts, fr.ts, es.ts, it.ts, nl.ts files exist in the package and export valid KeksmeisterTranslations, but the consumer is responsible for registerTranslation('pl', pl) before the banner reads its config.

The README mentions extending with custom translations as a follow-up sentence, but the foot-gun is: the wrong language doesn't fail — it silently renders in German. A consumer in production might never notice that their Polish/French users see a German cookie banner.

Suggested fixes (pick whichever fits the design)

  1. Auto-resolve bundled locales on demand. When resolveTranslations(lang) does not find a registered table for a locale that does exist on disk under i18n/, dynamically import + register it before returning. Tree-shakable per-locale chunks; common path (de/en) stays inlined. This makes lang=\"pl\" Just Work for every locale the package already ships.
  2. Warn on silent fallback in dev. If lang is provided, not found in either map, and the value isn't de, emit a console.warn (gated on import.meta.env.DEV or equivalent) along the lines of: "keksmeister: no translation registered for 'pl' — falling back to 'de'. Import and registerTranslation from 'keksmeister/i18n/pl' to enable Polish."
  3. Document the side-loading footgun. Add an explicit code block to the README showing the registerTranslation flow for the locales shipped under i18n/. Currently the README only says "German and English built-in, extensible with custom translations" which reads as: the others need custom tables, not as: the others ship in the package and just need registering.

(1) is the user-friendliest. (2) is the cheapest. (3) at minimum closes the documentation gap.

Context

Hit while rolling out multilingual support on apps/homepage in footage.one/frontend. Worked around it consumer-side by side-loading the matching keksmeister/i18n/<lang> chunk and calling registerTranslation inside our CookieConsentService.init(). See footage.one/frontend@e4a3c2fa1 for the workaround. Filing this so the next consumer doesn't have to discover the same trap by eye-balling their Polish banner.

Verified against

keksmeister@0.4.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions