Skip to content

Instantly share code, notes, and snippets.

@tomoasleep
Last active December 17, 2022 19:40
Show Gist options
  • Save tomoasleep/5f4143edc7dcbe79abd5b7438a5e601a to your computer and use it in GitHub Desktop.
Save tomoasleep/5f4143edc7dcbe79abd5b7438a5e601a to your computer and use it in GitHub Desktop.
// Returns R if T is a function, otherwise returns Fallback
type IsFunction<T, R, Fallback = T> = T extends (...args: any[]) => any ? R : Fallback
// Returns R if T is an object, otherwise returns Fallback
type IsObject<T, R, Fallback = T> = IsFunction<T, Fallback, (T extends object ? R : Fallback)>
// "a.b.c" => "b.c"
type Tail<S> = S extends `${string}.${infer T}`? Tail<T> : S;
// typeof Object.values(T)
type Value<T> = T[keyof T]
// {a: {b: 1, c: 2}} => {"a.b": {b: 1, c: 2}, "a.c": {b: 1, c: 2}}
type FlattenStepOne<T> = {
[K in keyof T as K extends string ? (IsObject<T[K], `${K}.${keyof T[K] & string}`, K>) : K]:
IsObject<T[K], {[key in keyof T[K]]: T[K][key]}>
};
// {"a.b": {b: 1, c: 2}, "a.c": {b: 1, c: 2}} => {"a.b": 1, "a.c": 2}
type FlattenStepTwo<T> = {[a in keyof T]: IsObject<T[a], Value<{[M in keyof T[a] as M extends Tail<a> ? M : never]: T[a][M] }>>}
// {a: {b: 1, c: {d: 1}}} => {"a.b": 1, "a.c": {d: 1}}
type FlattenOneLevel<T> = FlattenStepTwo<FlattenStepOne<T>>
// {a: {b: 1, c: {d: 1}}} => {"a.b": 1, "a.b.c.d": 1}
type Flatten<T> = T extends FlattenOneLevel<T> ? T: Flatten<FlattenOneLevel<T>>
const dictionaryJA = {
editor: {
placeholders: {
title: "タイトル",
tag: "タグを入力 (例: Ruby)"
},
slideMode: "スライドモード",
submit: ({ group } : { group: string }) => `${group} に投稿`,
},
} as const
const dictionaries = { ja: dictionaryJA } as const
type I18nParam<F> = F extends ((...args: any) => any) ? Parameters<F> : []
type Dictionary = { [K in string]: Dictionary | string | ((...args: any) => string) }
function translate<D extends Dictionary, K extends keyof Flatten<D> & string, P extends I18nParam<Flatten<D>[K]>>(dictionaries: D, key: K, ...params: P): string {
const flattenedDictionary = flattenDictionaries(dictionaries)
const value = flattenedDictionary[key]
switch (typeof value) {
case 'function':
if (params.length >= value.length) {
return value.apply(null, params)
} else {
throw `parameter missing: ${key}`
}
case 'string':
return value
default:
throw `translation missing: ${key}`
}
}
// {a: {b: "text", c: {d: (key) => `${key} text` }}} => {"a.b": "text", "a.b.c.d": (key) => `${key} text` }
function flattenDictionaries<D extends Dictionary>(dictionaries: D): Flatten<D>{
return Object.keys(dictionaries).reduce((dict: Dictionary, key) => {
const value = dictionaries[key]
if (typeof value === 'object') {
Object.entries(flattenDictionaries(value)).forEach(([k, v]) => {
dict[`${key}.${k}`] = v
})
} else {
dict[key] = value
}
return dict
}, {}) as Flatten<D>
}
console.log(translate(dictionaries, "ja.editor.placeholders.tag"));
console.log(translate(dictionaries, "ja.editor.submit", { group: "Qiita" }));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment