From bc807a49465d3752ea61ed04b2b5d9298de83abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Comeau?= <114004123+sebastien-comeau@users.noreply.github.com> Date: Thu, 27 Feb 2025 08:04:51 -0400 Subject: [PATCH] feat(frontend): throw i18next missing key error (#287) --- frontend/app/errors/error-codes.ts | 3 +++ frontend/app/i18n-config.server.ts | 22 +++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/app/errors/error-codes.ts b/frontend/app/errors/error-codes.ts index 36e78b78..45fa1e6c 100644 --- a/frontend/app/errors/error-codes.ts +++ b/frontend/app/errors/error-codes.ts @@ -33,4 +33,7 @@ export const ErrorCodes = { // dev-only error codes TEST_ERROR_CODE: 'DEV-0001', + + // i18n error codes + MISSING_TRANSLATION_KEY: 'I18N-0001', } as const; diff --git a/frontend/app/i18n-config.server.ts b/frontend/app/i18n-config.server.ts index b44ed695..a4fa27e6 100644 --- a/frontend/app/i18n-config.server.ts +++ b/frontend/app/i18n-config.server.ts @@ -1,4 +1,4 @@ -import type { i18n, Namespace, TFunction } from 'i18next'; +import type { i18n, KeyPrefix, Namespace, TFunction } from 'i18next'; import { createInstance } from 'i18next'; import { initReactI18next } from 'react-i18next'; @@ -14,13 +14,15 @@ import { getLanguage } from '~/utils/i18n-utils'; * * @param languageOrRequest - The language code or Request object to get the language from. * @param namespace - The namespace to get the translation function for. + * @param keyPrefix - The key prefix to use for the translation function. * @returns A translation function for the given language and namespace. * @throws {AppError} If no language is found in the `languageOrRequest`. */ -export async function getFixedT( +export async function getFixedT = undefined>( languageOrRequest: Language | Request, namespace: NS, -): Promise> { + keyPrefix?: TKPrefix, +): Promise> { const isRequest = languageOrRequest instanceof Request; const language = isRequest // @@ -32,7 +34,7 @@ export async function getFixedT( } const i18n = await initI18next(language); - return i18n.getFixedT(language, namespace); + return i18n.getFixedT(language, namespace, keyPrefix); } /** @@ -41,20 +43,22 @@ export async function getFixedT( * * @param languageOrRequest - The language code or Request object to get the language from. * @param namespace - The namespace to get the translation function for. + * @param keyPrefix - The key prefix to use for the translation function. * @returns A Promise resolving to an object containing the language code (`lang`) and a translation function (`t`) for the given namespace. * @throws {AppError} If no language is found in the `languageOrRequest`. */ -export async function getTranslation( +export async function getTranslation = undefined>( languageOrRequest: Language | Request, namespace: NS, -): Promise<{ lang: Language; t: TFunction }> { + keyPrefix?: TKPrefix, +): Promise<{ lang: Language; t: TFunction }> { const lang = getLanguage(languageOrRequest); if (lang === undefined) { throw new AppError('No language found in request', ErrorCodes.NO_LANGUAGE_FOUND); } - return { lang, t: await getFixedT(languageOrRequest, namespace) }; + return { lang, t: await getFixedT(languageOrRequest, namespace, keyPrefix) }; } /** @@ -78,6 +82,10 @@ export async function initI18next(language?: Language): Promise { ns: namespaces, resources: i18nResources, interpolation: { escapeValue: false }, + appendNamespaceToMissingKey: true, + parseMissingKeyHandler: (key) => { + throw new AppError(`Missing translation key: ${key}`, ErrorCodes.MISSING_TRANSLATION_KEY); + }, }); return i18n;