@xndrjs/i18n 0.3.3 → 0.5.0-alpha.0
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/README.md +193 -44
- package/dist/codegen/index.d.ts +85 -0
- package/dist/codegen/index.js +181 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/index.d.ts +34 -18
- package/dist/index.js +174 -145
- package/dist/index.js.map +1 -1
- package/dist/validation/index.d.ts +86 -4
- package/dist/validation/index.js.map +1 -1
- package/package.json +6 -1
- package/src/IcuTranslationProviderMulti.test.ts +48 -1
- package/src/IcuTranslationProviderMulti.ts +41 -61
- package/src/IcuTranslationProviderSingle.test.ts +67 -0
- package/src/IcuTranslationProviderSingle.ts +27 -51
- package/src/audit/audit-dictionaries.ts +4 -1
- package/src/audit/run-audit.test.ts +35 -1
- package/src/audit/run-audit.ts +15 -7
- package/src/codegen/codegen-config-schema.ts +68 -1
- package/src/codegen/config.test.ts +174 -39
- package/src/codegen/config.ts +14 -5
- package/src/codegen/constants.ts +8 -0
- package/src/codegen/delivery-artifacts.test.ts +142 -0
- package/src/codegen/delivery-artifacts.ts +124 -0
- package/src/codegen/emit/dictionary-file.test.ts +190 -0
- package/src/codegen/emit/dictionary-file.ts +165 -2
- package/src/codegen/emit/dictionary-schema-file.ts +5 -0
- package/src/codegen/emit/instance-file.ts +119 -38
- package/src/codegen/emit/namespace-loaders-file.test.ts +176 -0
- package/src/codegen/emit/namespace-loaders-file.ts +118 -32
- package/src/codegen/emit/types-file.test.ts +114 -0
- package/src/codegen/emit/types-file.ts +48 -11
- package/src/codegen/generate-i18n-types.test.ts +765 -22
- package/src/codegen/generate-i18n-types.ts +111 -58
- package/src/codegen/icu-analysis.ts +9 -2
- package/src/codegen/locale-fallback.ts +19 -10
- package/src/codegen/locale-policy.ts +4 -0
- package/src/codegen/paths.ts +9 -5
- package/src/codegen/project-locales-set-namespace.test.ts +11 -9
- package/src/codegen/read-dictionary.test.ts +474 -2
- package/src/codegen/read-dictionary.ts +164 -15
- package/src/codegen/write-file-if-changed.test.ts +42 -0
- package/src/codegen/write-file-if-changed.ts +20 -0
- package/src/codegen-config/build-config.ts +34 -0
- package/src/codegen-config/codegen-config.test.ts +32 -0
- package/src/codegen-config/index.ts +21 -0
- package/src/codegen-config/write-config.ts +8 -0
- package/src/deep-freeze.test.ts +13 -0
- package/src/deep-freeze.ts +5 -0
- package/src/format-core.ts +91 -0
- package/src/index.ts +8 -5
- package/src/project-locales.test.ts +129 -10
- package/src/project-locales.ts +90 -3
- package/src/setup/setup-i18n.test.ts +1 -1
- package/src/setup/setup-i18n.ts +9 -32
- package/src/types.ts +19 -4
- package/src/validation/normalize.ts +4 -0
- package/dist/types-C1CpXVOJ.d.ts +0 -83
- package/src/ensure-namespace.integration.test.ts +0 -66
- package/src/ensure-namespace.test.ts +0 -125
- package/src/ensure-namespace.ts +0 -70
- /package/src/{setup → codegen-config}/type-names.ts +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { formatTypesFile } from "./types-file.js";
|
|
4
|
+
|
|
5
|
+
const projectRoot = "/project";
|
|
6
|
+
const typesOutputPath = path.join(projectRoot, "src/i18n/generated/i18n-types.generated.ts");
|
|
7
|
+
|
|
8
|
+
describe("formatTypesFile", () => {
|
|
9
|
+
it("emits explicit Partial schema types for multi-namespace", () => {
|
|
10
|
+
const output = formatTypesFile({
|
|
11
|
+
isSingle: false,
|
|
12
|
+
entries: [
|
|
13
|
+
{ namespace: "default", filePath: "src/i18n/translations/default.json" },
|
|
14
|
+
{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" },
|
|
15
|
+
],
|
|
16
|
+
projectRoot,
|
|
17
|
+
typesOutputPath,
|
|
18
|
+
paramsTypeName: "AppParams",
|
|
19
|
+
schemaTypeName: "AppSchema",
|
|
20
|
+
localeTypeName: "AppLocale",
|
|
21
|
+
localeFallbackConstName: "LOCALE_FALLBACK",
|
|
22
|
+
localeFallbackTypeName: "AppLocaleFallback",
|
|
23
|
+
paramsByNamespace: {
|
|
24
|
+
default: {
|
|
25
|
+
login_button: "never",
|
|
26
|
+
welcome: "{ name: string }",
|
|
27
|
+
},
|
|
28
|
+
billing: {
|
|
29
|
+
invoice_summary: "{ count: number }",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
requestLocaleUnion: "'en' | 'it'",
|
|
33
|
+
hasLazy: true,
|
|
34
|
+
loadOnInitSet: new Set(["default"]),
|
|
35
|
+
lazyEntries: [{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" }],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(output).toContain("login_button: Partial<Record<AppLocale, string>>;");
|
|
39
|
+
expect(output).toContain("invoice_summary: Partial<Record<AppLocale, string>>;");
|
|
40
|
+
expect(output).not.toContain("typeof import");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("emits explicit Partial schema types for single-file mode", () => {
|
|
44
|
+
const output = formatTypesFile({
|
|
45
|
+
isSingle: true,
|
|
46
|
+
entries: [{ namespace: "translations", filePath: "src/i18n/translations/translations.yaml" }],
|
|
47
|
+
projectRoot,
|
|
48
|
+
typesOutputPath,
|
|
49
|
+
paramsTypeName: "AppParams",
|
|
50
|
+
schemaTypeName: "AppSchema",
|
|
51
|
+
localeTypeName: "AppLocale",
|
|
52
|
+
localeFallbackConstName: "LOCALE_FALLBACK",
|
|
53
|
+
localeFallbackTypeName: "AppLocaleFallback",
|
|
54
|
+
paramsByNamespace: {
|
|
55
|
+
translations: {
|
|
56
|
+
welcome: "{ name: string }",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
requestLocaleUnion: "'en' | 'it'",
|
|
60
|
+
hasLazy: false,
|
|
61
|
+
loadOnInitSet: new Set(),
|
|
62
|
+
lazyEntries: [],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(output).toContain(
|
|
66
|
+
"export type AppSchema = {\n welcome: Partial<Record<AppLocale, string>>;\n};"
|
|
67
|
+
);
|
|
68
|
+
expect(output).not.toContain("typeof import");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("emits delivery area type for custom delivery", () => {
|
|
72
|
+
const output = formatTypesFile({
|
|
73
|
+
isSingle: false,
|
|
74
|
+
entries: [
|
|
75
|
+
{ namespace: "default", filePath: "src/i18n/translations/default.json" },
|
|
76
|
+
{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" },
|
|
77
|
+
],
|
|
78
|
+
projectRoot,
|
|
79
|
+
typesOutputPath,
|
|
80
|
+
paramsTypeName: "AppParams",
|
|
81
|
+
schemaTypeName: "AppSchema",
|
|
82
|
+
localeTypeName: "AppLocale",
|
|
83
|
+
localeFallbackConstName: "LOCALE_FALLBACK",
|
|
84
|
+
localeFallbackTypeName: "AppLocaleFallback",
|
|
85
|
+
localeFallback: {
|
|
86
|
+
"en-US": null,
|
|
87
|
+
it: "en-US",
|
|
88
|
+
fr: null,
|
|
89
|
+
},
|
|
90
|
+
paramsByNamespace: {
|
|
91
|
+
default: { some_key: "never" },
|
|
92
|
+
billing: { invoice_summary: "{ count: number }" },
|
|
93
|
+
},
|
|
94
|
+
requestLocaleUnion: "'en-US' | 'fr' | 'it'",
|
|
95
|
+
deliveryAreaTypeName: "AppDeliveryArea",
|
|
96
|
+
deliveryAreaUnion: "'eu' | 'us'",
|
|
97
|
+
deliveryArtifacts: {
|
|
98
|
+
eu: ["it", "fr"],
|
|
99
|
+
us: ["en-US"],
|
|
100
|
+
},
|
|
101
|
+
hasLazy: true,
|
|
102
|
+
loadOnInitSet: new Set(["default"]),
|
|
103
|
+
lazyEntries: [{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" }],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(output).toContain("export type AppLocale = 'en-US' | 'fr' | 'it';");
|
|
107
|
+
expect(output).toContain("export type AppDeliveryArea = 'eu' | 'us';");
|
|
108
|
+
expect(output).toContain("export const LOCALE_DELIVERY_AREA = {");
|
|
109
|
+
expect(output).toContain('"it": "eu"');
|
|
110
|
+
expect(output).toContain('"en-US": "us"');
|
|
111
|
+
expect(output).toContain("} as const satisfies Record<AppLocale, AppDeliveryArea>;");
|
|
112
|
+
expect(output).toContain("some_key: Partial<Record<AppLocale, string>>;");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { formatLocaleDeliveryAreaBlock, type DeliveryArtifactsMap } from "../delivery-artifacts.js";
|
|
2
2
|
import { formatLocaleFallbackBlock } from "../locale-fallback.js";
|
|
3
|
-
import { GENERATED_FILE_BANNER
|
|
3
|
+
import { GENERATED_FILE_BANNER } from "../paths.js";
|
|
4
4
|
import type { NamespaceEntry } from "../types.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Emits the generated `i18n-types.generated.ts` module: schema, params, locale/area
|
|
8
|
+
* unions, fallback constants, and lazy-load type aliases.
|
|
9
|
+
*/
|
|
6
10
|
export function formatLazyTypesBlock(
|
|
7
11
|
loadOnInitSet: Set<string>,
|
|
8
12
|
lazyEntries: NamespaceEntry[],
|
|
@@ -21,6 +25,13 @@ export function formatLazyTypesBlock(
|
|
|
21
25
|
);
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
export function formatLocaleTemplateType(localeTypeName: string, hasLocaleUnion: boolean): string {
|
|
29
|
+
if (!hasLocaleUnion) {
|
|
30
|
+
return "Partial<Record<string, string>>";
|
|
31
|
+
}
|
|
32
|
+
return `Partial<Record<${localeTypeName}, string>>`;
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
export interface TypesFileOptions {
|
|
25
36
|
isSingle: boolean;
|
|
26
37
|
entries: NamespaceEntry[];
|
|
@@ -34,6 +45,10 @@ export interface TypesFileOptions {
|
|
|
34
45
|
localeFallback?: Record<string, string | null> | undefined;
|
|
35
46
|
paramsByNamespace: Record<string, Record<string, string>>;
|
|
36
47
|
requestLocaleUnion: string;
|
|
48
|
+
deliveryAreaTypeName?: string;
|
|
49
|
+
deliveryAreaUnion?: string;
|
|
50
|
+
deliveryArtifacts?: DeliveryArtifactsMap;
|
|
51
|
+
localeDeliveryAreaConstName?: string;
|
|
37
52
|
hasLazy: boolean;
|
|
38
53
|
loadOnInitSet: Set<string>;
|
|
39
54
|
lazyEntries: NamespaceEntry[];
|
|
@@ -43,8 +58,6 @@ export function formatTypesFile(options: TypesFileOptions): string {
|
|
|
43
58
|
const {
|
|
44
59
|
isSingle,
|
|
45
60
|
entries,
|
|
46
|
-
projectRoot,
|
|
47
|
-
typesOutputPath,
|
|
48
61
|
paramsTypeName,
|
|
49
62
|
schemaTypeName,
|
|
50
63
|
localeTypeName,
|
|
@@ -53,16 +66,36 @@ export function formatTypesFile(options: TypesFileOptions): string {
|
|
|
53
66
|
localeFallback,
|
|
54
67
|
paramsByNamespace,
|
|
55
68
|
requestLocaleUnion,
|
|
69
|
+
deliveryAreaTypeName,
|
|
70
|
+
deliveryAreaUnion,
|
|
71
|
+
deliveryArtifacts,
|
|
72
|
+
localeDeliveryAreaConstName = "LOCALE_DELIVERY_AREA",
|
|
56
73
|
hasLazy,
|
|
57
74
|
loadOnInitSet,
|
|
58
75
|
lazyEntries,
|
|
59
76
|
} = options;
|
|
60
77
|
|
|
78
|
+
const hasLocaleUnion = Boolean(requestLocaleUnion);
|
|
79
|
+
const localeTemplateType = formatLocaleTemplateType(localeTypeName, hasLocaleUnion);
|
|
80
|
+
|
|
61
81
|
const localeBlock = requestLocaleUnion
|
|
62
82
|
? `${localeFallback ? formatLocaleFallbackBlock(localeFallback, localeFallbackConstName, localeFallbackTypeName) : ""}` +
|
|
63
83
|
`export type ${localeTypeName} = ${requestLocaleUnion};\n\n`
|
|
64
84
|
: "";
|
|
65
85
|
|
|
86
|
+
const deliveryAreaBlock =
|
|
87
|
+
deliveryAreaTypeName && deliveryAreaUnion
|
|
88
|
+
? `export type ${deliveryAreaTypeName} = ${deliveryAreaUnion};\n\n` +
|
|
89
|
+
(deliveryArtifacts
|
|
90
|
+
? formatLocaleDeliveryAreaBlock(
|
|
91
|
+
deliveryArtifacts,
|
|
92
|
+
localeDeliveryAreaConstName,
|
|
93
|
+
localeTypeName,
|
|
94
|
+
deliveryAreaTypeName
|
|
95
|
+
)
|
|
96
|
+
: "")
|
|
97
|
+
: "";
|
|
98
|
+
|
|
66
99
|
let paramsBlock: string;
|
|
67
100
|
let schemaBlock: string;
|
|
68
101
|
|
|
@@ -75,11 +108,11 @@ export function formatTypesFile(options: TypesFileOptions): string {
|
|
|
75
108
|
|
|
76
109
|
paramsBlock = `export type ${paramsTypeName} = {\n${paramsLines}\n};`;
|
|
77
110
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
schemaBlock = `export type ${schemaTypeName} =
|
|
111
|
+
const schemaLines = Object.keys(keyTypes)
|
|
112
|
+
.map((key) => ` ${key}: ${localeTemplateType};`)
|
|
113
|
+
.join("\n");
|
|
114
|
+
|
|
115
|
+
schemaBlock = `export type ${schemaTypeName} = {\n${schemaLines}\n};`;
|
|
83
116
|
} else {
|
|
84
117
|
const namespaceBlocks = entries
|
|
85
118
|
.map((entry) => {
|
|
@@ -95,8 +128,11 @@ export function formatTypesFile(options: TypesFileOptions): string {
|
|
|
95
128
|
|
|
96
129
|
const schemaLines = entries
|
|
97
130
|
.map((entry) => {
|
|
98
|
-
const
|
|
99
|
-
|
|
131
|
+
const keyTypes = paramsByNamespace[entry.namespace] ?? {};
|
|
132
|
+
const lines = Object.keys(keyTypes)
|
|
133
|
+
.map((key) => ` ${key}: ${localeTemplateType};`)
|
|
134
|
+
.join("\n");
|
|
135
|
+
return ` ${entry.namespace}: {\n${lines}\n };`;
|
|
100
136
|
})
|
|
101
137
|
.join("\n");
|
|
102
138
|
|
|
@@ -111,6 +147,7 @@ export function formatTypesFile(options: TypesFileOptions): string {
|
|
|
111
147
|
`${GENERATED_FILE_BANNER}` +
|
|
112
148
|
`export const I18N_MODE = '${isSingle ? "single" : "multi"}' as const;\n\n` +
|
|
113
149
|
`${localeBlock}` +
|
|
150
|
+
`${deliveryAreaBlock}` +
|
|
114
151
|
`${paramsBlock}\n\n` +
|
|
115
152
|
`${schemaBlock}\n` +
|
|
116
153
|
`${lazyTypesBlock}`
|