diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 9484f32365..c4098ecafe 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -10,6 +10,7 @@ "allowJs": false, "esModuleInterop": true, "moduleResolution": "node", + "resolveJsonModule": true, "strictNullChecks": true, "strict": true, "plugins": [{ "name": "typescript-plugin-css-modules" }] diff --git a/packages/shared/localize.ts b/packages/shared/localize.ts index 2021154e71..cdbad4298f 100644 --- a/packages/shared/localize.ts +++ b/packages/shared/localize.ts @@ -1,4 +1,5 @@ import { getLogger } from './logger.js' +import { TranslationKey } from './translationKeyType.js' const log = getLogger('localize') export interface LocaleData { @@ -12,12 +13,18 @@ export interface LocaleData { } } +// Solution to typescript thinking our variants can be just combined into the type string +// adapted from https://github.com/sindresorhus/type-fest/blob/cabce984e5c19558f2f0061c3cd9488a945f60e6/source/literal-union.d.ts +export type LiteralUnion = + | LiteralType + | (string & Record) + // 'other' should exists for all languages (source?) // https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html type getMessageOptions = { quantity?: 'other' | number } export type getMessageFunction = ( - key: string, + key: LiteralUnion, substitutions?: string | string[], raw_opts?: 'other' | getMessageOptions ) => string @@ -44,19 +51,20 @@ export function translate( } function getMessage( - key: string, + key: LiteralUnion, substitutions?: string | string[], raw_opts?: 'other' | getMessageOptions ) { + const translationKey = key as string let opts: getMessageOptions = {} if (typeof raw_opts === 'string') opts = { quantity: raw_opts } else opts = Object.assign({}, raw_opts) - const entry = messages[key] + const entry = messages[translationKey] if (!entry) { - log.error(`Missing translation for key '${key}'`) - return key + log.error(`Missing translation for key '${translationKey}'`) + return translationKey } let message: string | undefined = entry.message @@ -89,16 +97,18 @@ export function translate( message = undefined } if (typeof message === 'undefined') { - log.error(`Missing quantity '${opts.quantity}' for key '${key}'`) - return `${key}:${opts.quantity}` + log.error( + `Missing quantity '${opts.quantity}' for key '${translationKey}'` + ) + return `${translationKey}:${opts.quantity}` } } if (typeof message === 'undefined') { log.error( - `Missing 'message' for key '${key}', maybe you need to specify quantity` + `Missing 'message' for key '${translationKey}', maybe you need to specify quantity` ) - return `${key}:?` + return `${translationKey}:?` } if (substitutions) { @@ -112,7 +122,7 @@ export function translate( substitutions === undefined || typeof substitutions[c] === 'undefined' ) { - log.error(`Missing ${c} argument for key %c'${key}'`) + log.error(`Missing ${c} argument for key %c'${translationKey}'`) return '' } return substitutions[c++].toString() diff --git a/packages/shared/translationKeyType.d.ts b/packages/shared/translationKeyType.d.ts new file mode 100644 index 0000000000..8496babc7c --- /dev/null +++ b/packages/shared/translationKeyType.d.ts @@ -0,0 +1,11 @@ +//@ts-ignore +import type english from '../../_locales/en.json' +//@ts-ignore +import type untranslated from '../../_locales/_untranslated_en.json' + +//@ts-ignore +export type TranslationKey = keyof typeof english | keyof typeof untranslated + +// The ignore here is to make it optional, +// if the import fails (because resolveJsonModule=false) it should bring down everything with it. +// currently this type is not used for checking, but just for autocompletion diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 219b7c47c3..5b9bd675fb 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -8,8 +8,9 @@ "allowJs": false, "strictNullChecks": true, "strict": true, + "resolveJsonModule": true, "moduleResolution": "NodeNext" }, - "include": ["./**/*.ts", "./**/*.js"], + "include": ["./**/*.ts", "./**/*.js", "../../_locales/*.json"], "exclude": ["./ts-compiled-for-tests"] } diff --git a/packages/target-browser/tsconfig.json b/packages/target-browser/tsconfig.json index 485724f9ad..20e3ead678 100644 --- a/packages/target-browser/tsconfig.json +++ b/packages/target-browser/tsconfig.json @@ -10,6 +10,7 @@ "allowJs": false, "esModuleInterop": true, "moduleResolution": "Node10", + "resolveJsonModule": true, "strictNullChecks": true, "strict": true }, diff --git a/packages/target-electron/src/load-translations.ts b/packages/target-electron/src/load-translations.ts index b6ae86d58d..e67fc05ef3 100644 --- a/packages/target-electron/src/load-translations.ts +++ b/packages/target-electron/src/load-translations.ts @@ -27,7 +27,7 @@ let translateFunction: getMessageFunction | null = null export const tx: getMessageFunction = function (key, substitutions, raw_opts) { if (translateFunction === null) { log.error('tried to use translation function before init') - return key + return key as string } return translateFunction(key, substitutions, raw_opts) }