@adrienlcp/i18n 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-i18n.d.ts +13 -1
- package/dist/dictionary.d.ts +5 -0
- package/dist/translator.js +20 -7
- package/package.json +5 -6
package/dist/create-i18n.d.ts
CHANGED
|
@@ -25,6 +25,18 @@ type Localized<Reference> = Dictionary & DictionaryFor<Reference>;
|
|
|
25
25
|
type AnyLoader = () => Promise<{
|
|
26
26
|
default: Dictionary;
|
|
27
27
|
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Refuses a `defaultLocale` typed as more than one locale. `const
|
|
30
|
+
* DEFAULT_LOCALE: Locale = 'en'` holds `'en'` but is typed `'en' | 'fr'`, which
|
|
31
|
+
* would make the reference every locale's dictionary at once: a second locale's
|
|
32
|
+
* leaves are plain `string`s, so every key with a placeholder would come out as
|
|
33
|
+
* `never` at the call site, far from the cause. The property name is the error
|
|
34
|
+
* TypeScript prints here instead.
|
|
35
|
+
*/
|
|
36
|
+
type SingleLocale<DefaultLocale extends string> = IsUnion<DefaultLocale> extends false ? unknown : {
|
|
37
|
+
'defaultLocale must be typed as one locale, not a union: write `const DEFAULT_LOCALE = "en" satisfies Locale`': never;
|
|
38
|
+
};
|
|
39
|
+
type IsUnion<Member, Whole = Member> = Member extends unknown ? [Whole] extends [Member] ? false : true : never;
|
|
28
40
|
/**
|
|
29
41
|
* One locale's entry held to the reference, whichever way it arrives. A loader
|
|
30
42
|
* is compared through the dictionary its module `export default`s, so a locale
|
|
@@ -110,7 +122,7 @@ export type I18n<Reference, Locale extends string, DefaultLocale extends Locale
|
|
|
110
122
|
* `load` resolves on the spot.
|
|
111
123
|
*/
|
|
112
124
|
export declare const createI18n: <const Entries extends Record<string, Dictionary | AnyLoader>, const DefaultLocale extends keyof Entries & string>({ defaultLocale, dictionaries }: {
|
|
113
|
-
defaultLocale: DefaultLocale
|
|
125
|
+
defaultLocale: DefaultLocale & SingleLocale<DefaultLocale>;
|
|
114
126
|
dictionaries: Entries & { [Locale in keyof Entries]: Matching<Entries[DefaultLocale], Entries[Locale]>; } & Record<DefaultLocale, Localized<Entries[DefaultLocale]>>;
|
|
115
127
|
}) => I18n<Entries[DefaultLocale], keyof Entries & string, DefaultLocale>;
|
|
116
128
|
export {};
|
package/dist/dictionary.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ export declare const defineDictionary: <const T extends { [Segment in keyof T]:
|
|
|
38
38
|
* TypeScript's excess property check does the other half of the work: a key the
|
|
39
39
|
* reference does not have is rejected at the literal, at every depth.
|
|
40
40
|
*
|
|
41
|
+
* Only for a dictionary handed to `createTranslator` directly. One registered
|
|
42
|
+
* with `createI18n` is written through `defineDictionary`, like the reference:
|
|
43
|
+
* the annotation widens each message to `string`, and the registry, which
|
|
44
|
+
* compares placeholders, then refuses every message that has one.
|
|
45
|
+
*
|
|
41
46
|
* ```ts
|
|
42
47
|
* export const FR_DICTIONARY: DictionaryFor<typeof EN_DICTIONARY> = { … }
|
|
43
48
|
* ```
|
package/dist/translator.js
CHANGED
|
@@ -106,9 +106,10 @@ const FORMATTED_COUNT = '{?}';
|
|
|
106
106
|
* That is dictionary text rather than a caller's value, so the rule above is
|
|
107
107
|
* untouched.
|
|
108
108
|
*
|
|
109
|
-
* A value of the wrong type,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
* A value of the wrong type, one the message never asked for, or one the
|
|
110
|
+
* locale cannot print — an invalid `Date`, an infinite count, a code that names
|
|
111
|
+
* no language — leaves its placeholder standing rather than throwing: one bad
|
|
112
|
+
* value costs one word, not the whole sentence.
|
|
112
113
|
*/
|
|
113
114
|
const substitute = ({ expanding = [], formatters, message, options, values }) => message.replace(PLACEHOLDER, (placeholder, name, type) => {
|
|
114
115
|
const value = values[name];
|
|
@@ -117,7 +118,7 @@ const substitute = ({ expanding = [], formatters, message, options, values }) =>
|
|
|
117
118
|
}
|
|
118
119
|
switch (type) {
|
|
119
120
|
case 'date':
|
|
120
|
-
return value
|
|
121
|
+
return isValidDate(value)
|
|
121
122
|
? formatters.date(options.date?.[name]).format(value)
|
|
122
123
|
: placeholder;
|
|
123
124
|
case 'displayname':
|
|
@@ -167,7 +168,7 @@ const substitute = ({ expanding = [], formatters, message, options, values }) =>
|
|
|
167
168
|
})
|
|
168
169
|
: placeholder;
|
|
169
170
|
case 'relative':
|
|
170
|
-
return typeof value === 'number'
|
|
171
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
171
172
|
? (relativeTime({
|
|
172
173
|
count: value,
|
|
173
174
|
formatters,
|
|
@@ -220,9 +221,21 @@ const pluralize = ({ count, formatters, forms }) => {
|
|
|
220
221
|
/**
|
|
221
222
|
* A message declaring `{x:displayname}` must declare the kind of name it wants,
|
|
222
223
|
* so the options are never absent — but the runtime shape stays loose, and
|
|
223
|
-
* `Intl.DisplayNames` throws without a `type
|
|
224
|
+
* `Intl.DisplayNames` throws without a `type`, and on a string that is no code
|
|
225
|
+
* at all (`'not a language'`). Nothing rather than a crash.
|
|
224
226
|
*/
|
|
225
|
-
const displayName = ({ formatters, of, options }) =>
|
|
227
|
+
const displayName = ({ formatters, of, options }) => {
|
|
228
|
+
if (options === undefined) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
return formatters.displayname(options).of(of);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const isValidDate = (value) => value instanceof Date && !Number.isNaN(value.getTime());
|
|
226
239
|
/** Same reasoning: the unit is declared with the message, or there is none. */
|
|
227
240
|
const relativeTime = ({ count, formatters, unit }) => unit === undefined
|
|
228
241
|
? undefined
|
package/package.json
CHANGED
|
@@ -24,13 +24,12 @@
|
|
|
24
24
|
"type": "git",
|
|
25
25
|
"url": "git+https://github.com/AdrienLcp/packages.git"
|
|
26
26
|
},
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"type": "module",
|
|
29
|
+
"version": "0.1.2",
|
|
27
30
|
"scripts": {
|
|
28
31
|
"build": "tsc -p tsconfig.build.json",
|
|
29
|
-
"prepublishOnly": "pnpm build",
|
|
30
32
|
"test": "vitest run",
|
|
31
33
|
"test:watch": "vitest --watch"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
"type": "module",
|
|
35
|
-
"version": "0.1.0"
|
|
36
|
-
}
|
|
34
|
+
}
|
|
35
|
+
}
|