Skip to content

Commit

Permalink
add autocompletion for translation keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon-Laux committed Oct 29, 2024
1 parent 77abbc9 commit 1c989ce
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"allowJs": false,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"strictNullChecks": true,
"strict": true,
"plugins": [{ "name": "typescript-plugin-css-modules" }]
Expand Down
30 changes: 20 additions & 10 deletions packages/shared/localize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getLogger } from './logger.js'
import { TranslationKey } from './translationKeyType.js'
const log = getLogger('localize')

export interface LocaleData {
Expand All @@ -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> =
| LiteralType
| (string & Record<never, never>)

// '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<TranslationKey>,
substitutions?: string | string[],
raw_opts?: 'other' | getMessageOptions
) => string
Expand All @@ -44,19 +51,20 @@ export function translate(
}

function getMessage(
key: string,
key: LiteralUnion<TranslationKey>,
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
Expand Down Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
11 changes: 11 additions & 0 deletions packages/shared/translationKeyType.d.ts
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion packages/shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
1 change: 1 addition & 0 deletions packages/target-browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"allowJs": false,
"esModuleInterop": true,
"moduleResolution": "Node10",
"resolveJsonModule": true,
"strictNullChecks": true,
"strict": true
},
Expand Down
2 changes: 1 addition & 1 deletion packages/target-electron/src/load-translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit 1c989ce

Please sign in to comment.