@number10/typekit-i18n
v0.3.0
Published
Type-safe i18n runtime and code generator for TypeScript.
Readme
typekit-i18n
Type-safe i18n runtime and code generator for TypeScript.
Links
- GitHub: https://github.com/Michael--/typekit-i18n
- Documentation: https://michael--.github.io/typekit-i18n/
- npm: https://www.npmjs.com/package/@number10/typekit-i18n
Installation
pnpm add @number10/typekit-i18nAlternative package managers:
npm install @number10/typekit-i18n
# or
yarn add @number10/typekit-i18nVSCode Extension
For translation authoring, the repository also ships a dedicated VSCode extension:
- Source:
packages/typekit-i18n-vscode - Marketplace link: https://marketplace.visualstudio.com/items?itemName=number10.typekit-i18n-vscode
Local VSIX build:
pnpm --filter typekit-i18n-vscode package:vsixExports
Runtime APIs:
import { createTranslator, createIcuTranslator } from '@number10/typekit-i18n'Optional full-ICU runtime (external engine via peer dependency):
import { createFormatjsIcuTranslator } from '@number10/typekit-i18n/runtime/icu-formatjs'Codegen APIs:
import { defineTypekitI18nConfig } from '@number10/typekit-i18n/codegen'CLI binary:
typekit-i18nFeature Overview
- Typed translator creation from generated key and language unions
- Fallback or strict error behavior for missing translations
- Placeholder replacement (
{name}) and formatter tokens ({amount|currency}) - ICU-capable translator for
plural,select,selectordinal,number,date,time - Translation generation from mixed CSV and YAML files
- Validation and format conversion via CLI
Runtime Footprint Guide
Choose runtime based on required ICU feature set vs runtime footprint:
createTranslator/runtimeBridgeMode: 'basic': Smallest runtime footprint. No ICU expression parsing/rendering.createIcuTranslator/runtimeBridgeMode: 'icu'(default): Medium footprint with built-in ICU subset support.createFormatjsIcuTranslator/runtimeBridgeMode: 'icu-formatjs': Largest footprint, broadest ICU compatibility viaintl-messageformat(optional peer dependency).
If your translations do not require ICU features, use basic for the smallest bundle/runtime footprint.
Quick Start
1. Create config (typekit.config.ts)
import { defineTypekitI18nConfig } from '@number10/typekit-i18n/codegen'
export default defineTypekitI18nConfig({
input: ['./translations/*.csv', './translations/*.yaml'],
output: './src/generated/translationTable.ts',
outputKeys: './src/generated/translationKeys.ts',
languages: ['en', 'de', 'fr'] as const,
defaultLanguage: 'en',
})2. Generate translation outputs
npx typekit-i18n
# same as: npx typekit-i18n generate3. Create a runtime translator
import { createTranslator } from '@number10/typekit-i18n'
import type { PlaceholderFormatterMap } from '@number10/typekit-i18n'
import type { TranslateKey, TranslateLanguage } from './generated/translationKeys'
import { translationTable } from './generated/translationTable'
const formatters: PlaceholderFormatterMap<TranslateKey, TranslateLanguage> = {
currency: (value) => `EUR ${value}`,
}
const t = createTranslator(translationTable, {
defaultLanguage: 'en',
missingStrategy: 'fallback',
formatters,
})
const title = t('greeting_title', 'de')
const price = t('price_formatted', 'de', {
data: [{ key: 'amount', value: 12.5 }],
})Runtime API
createTranslator(table, options?)
Creates a typed translator:
;(key, language?, placeholder?) => stringOptions:
defaultLanguage?: TLanguage(default:'en'when available in table)missingStrategy?: 'fallback' | 'strict'(default:'fallback')formatters?: PlaceholderFormatterMap<TKey, TLanguage>onMissingTranslation?: (event) => void
Missing reasons:
missingKeymissingLanguagemissingFallback
Behavior summary:
- Requested language value is used when non-empty.
- Empty requested language falls back to
defaultLanguage(or'en'when omitted). - If fallback is missing, translator returns the key (or throws in strict mode).
createIcuTranslator(table, options?)
Creates a typed translator with ICU support.
Additional option:
localeByLanguage?: Partial<Record<TLanguage, string>>
Supported ICU subset:
- Branch expressions:
{count, plural, =0 {...} one {...} other {...}}{count, plural, offset:1 one {...} other {...}}{gender, select, male {...} female {...} other {...}}{place, selectordinal, one {...} two {...} few {...} other {...}}
- Argument formatting:
{value, number}{value, number, percent}{value, number, currency/EUR}{dateValue, date, short}{dateValue, time, ::HH:mm}
#substitution inside plural/selectordinal branches- Apostrophe escaping (
'', quoted literals)
If ICU syntax is invalid, detailed runtime errors include key, language, and line/column location.
createFormatjsIcuTranslator(table, options?)
Creates a typed translator backed by intl-messageformat (FormatJS) for broader ICU compatibility.
Import path:
import { createFormatjsIcuTranslator } from '@number10/typekit-i18n/runtime/icu-formatjs'Notes:
intl-messageformatis an optional peer dependency and is only required when using this runtime.- Existing
{name|formatter}placeholders remain supported for backward compatibility.
Placeholder Types
type PlaceholderValue = string | number | boolean | bigint | Date
interface Placeholder {
data: ReadonlyArray<{ key: string; value: PlaceholderValue }>
}Runtime Helper APIs
Also exported:
createTranslationRuntime(table, options?)createConsoleMissingTranslationReporter(writer?)translate(...)configureTranslationRuntime(...)getCollectedMissingTranslations()clearCollectedMissingTranslations()
For application integrations, createTranslator or createIcuTranslator should be preferred.
Codegen Config API (@number10/typekit-i18n/codegen)
Main exports:
defineTypekitI18nConfig(...)generateTranslationTable(...)validateTranslationFile(...)validateYamlTranslationFile(...)loadTypekitI18nConfig(...)
Config shape:
interface TypekitI18nConfig<TLanguage extends string = string> {
input: string | ReadonlyArray<string>
format?: 'csv' | 'yaml'
output: string
outputKeys?: string
outputSwift?: string
outputKotlin?: string
outputRuntimeBridge?: string
outputRuntimeBridgeBundle?: string
runtimeBridgeMode?: 'basic' | 'icu' | 'icu-formatjs'
runtimeBridgeFunctionName?: string
outputContract?: string
languages: ReadonlyArray<TLanguage>
defaultLanguage: TLanguage
localeByLanguage?: Partial<Record<TLanguage, string>>
}Notes:
inputaccepts multiple files/globs and merges them deterministically.- Without
format, each file format is inferred by extension. - Duplicate keys across all inputs are rejected.
outputandoutputKeysmust not be the same file path.
Auto-discovered config file order when --config is omitted:
typekit.config.tstypekit.config.jsontypekit.config.yamltypekit.config.ymltypekit-i18n.config.tstypekit-i18n.config.jsontypekit-i18n.config.yamltypekit-i18n.config.yml
CLI
Binary name: typekit-i18n
generate (default)
typekit-i18n generate --config ./typekit.config.ts
# or simply:
typekit-i18n
# explicit targets:
typekit-i18n generate --target ts
typekit-i18n generate --target swift
typekit-i18n generate --target kotlin
typekit-i18n generate --target ts,swift,kotlin- Loads config and generates
translationTable.tsandtranslationKeys.ts - Also generates canonical
translation.contract.json(path configurable viaoutputContract) --target swiftalso generates Swift output (path configurable viaoutputSwift)--target kotlinalso generates Kotlin output (path configurable viaoutputKotlin)- Native targets also generate
translation.runtime.mjsby default (path configurable viaoutputRuntimeBridge) - Native targets also generate
translation.runtime.bundle.jsfor direct eval runtimes (path configurable viaoutputRuntimeBridgeBundle) - Runtime bridge mode is configurable via
runtimeBridgeMode(icudefault,basic, oricu-formatjs) runtimeBridgeMode: 'icu-formatjs'requires the optional peer dependencyintl-messageformat- If no config is found, exits successfully and skips generation
Native Runtime Smokes (Swift, Kotlin, Java)
Native integration smoke fixtures are part of this repository:
tests/fixtures/smoke-runtime/SmokeApp.swifttests/fixtures/smoke-runtime/SmokeApp.kttests/fixtures/smoke-runtime/SmokeApp.java- runner:
tests/fixtures/smoke-runtime/run-smoke.mjs
From repository root:
node packages/typekit-i18n/tests/fixtures/smoke-runtime/run-smoke.mjsThe smoke run generates artifacts and executes Swift, Kotlin, and Java examples against the generated runtime bridge bundle for each runtime bridge mode: basic, icu, and icu-formatjs.
To run a single runtime bridge mode:
TYPEKIT_RUNTIME_BRIDGE_MODE=icu-formatjs node packages/typekit-i18n/tests/fixtures/smoke-runtime/run-smoke.mjsvalidate
# YAML (format can be inferred from extension)
typekit-i18n validate --input ./translations/features.yaml
# CSV (requires CSV context)
typekit-i18n validate \
--input ./translations/ui.csv \
--format csv \
--languages en,de,fr \
--source-language enCSV validation requires:
--languages--source-language(or--sourceLanguage)
convert
# YAML -> CSV
typekit-i18n convert \
--from yaml \
--to csv \
--input ./translations/features.yaml \
--output ./translations/features.csv
# CSV -> YAML (requires CSV context)
typekit-i18n convert \
--from csv \
--to yaml \
--input ./translations/ui.csv \
--output ./translations/ui.yaml \
--languages en,de,fr \
--source-language enResource Formats
CSV
- Delimiter auto-detection:
;or, - Required columns:
keydescription- one column per configured language
- Optional columns:
status(draft | review | approved)tags(comma-separated)placeholders(comma-separated definitions:name:type:formatHint)
Example:
key;description;status;tags;placeholders;en;de
greeting_title;Main title;approved;ui,home;name:string;Welcome {name};Willkommen {name}
price_formatted;Price line;review;billing;amount:number:currency;Price {amount|currency};Preis {amount|currency}YAML
version: '1'
sourceLanguage: en
languages:
- en
- de
entries:
- key: greeting_title
description: Main title
status: approved
tags: [ui, home]
placeholders:
- name: name
type: string
values:
en: 'Welcome {name}'
de: 'Willkommen {name}'Validation Guarantees
Validation covers:
- schema correctness
- non-empty key and description
- language declaration consistency
- source/default language completeness
- placeholder declaration consistency
- placeholder token parity across all languages
- duplicate key detection
Notes
- Generated key unions preserve exact key strings.
- Use identifier-friendly keys (for example
snake_case) for best developer experience. - For workspace-level docs and deployment details, see the repository root README.
