@xndrjs/i18n 0.8.1 → 0.8.2-alpha.1

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.
Files changed (93) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/audit.js +739 -0
  3. package/dist/cli/audit.js.map +1 -0
  4. package/dist/cli/codegen.js +1471 -0
  5. package/dist/cli/codegen.js.map +1 -0
  6. package/dist/cli/setup.js +148 -0
  7. package/dist/cli/setup.js.map +1 -0
  8. package/dist/codegen/index.js +43 -40
  9. package/dist/codegen/index.js.map +1 -1
  10. package/package.json +6 -9
  11. package/bin/audit.mjs +0 -14
  12. package/bin/codegen.mjs +0 -17
  13. package/bin/setup.mjs +0 -14
  14. package/src/IcuTranslationProviderMulti.test.ts +0 -215
  15. package/src/IcuTranslationProviderMulti.ts +0 -188
  16. package/src/audit/audit-dictionaries.test.ts +0 -134
  17. package/src/audit/audit-dictionaries.ts +0 -178
  18. package/src/audit/run-audit.test.ts +0 -169
  19. package/src/audit/run-audit.ts +0 -94
  20. package/src/builder-load-registry.test.ts +0 -44
  21. package/src/builder-load-registry.ts +0 -78
  22. package/src/builder-loaders.ts +0 -15
  23. package/src/builder-types.test.ts +0 -57
  24. package/src/builder-types.ts +0 -24
  25. package/src/builder.test.ts +0 -303
  26. package/src/builder.ts +0 -15
  27. package/src/codegen/codegen-config-schema.ts +0 -156
  28. package/src/codegen/config.test.ts +0 -231
  29. package/src/codegen/config.ts +0 -42
  30. package/src/codegen/constants.ts +0 -9
  31. package/src/codegen/delivery-artifacts.test.ts +0 -167
  32. package/src/codegen/delivery-artifacts.ts +0 -154
  33. package/src/codegen/dictionary-spec-contract.test.ts +0 -28
  34. package/src/codegen/dictionary-spec-contract.ts +0 -138
  35. package/src/codegen/emit/dictionary-schema-file.ts +0 -113
  36. package/src/codegen/emit/instance-file.test.ts +0 -71
  37. package/src/codegen/emit/instance-file.ts +0 -257
  38. package/src/codegen/emit/namespace-loaders-file.test.ts +0 -251
  39. package/src/codegen/emit/namespace-loaders-file.ts +0 -229
  40. package/src/codegen/emit/types-file.test.ts +0 -96
  41. package/src/codegen/emit/types-file.ts +0 -142
  42. package/src/codegen/fixtures/billing-namespace.json +0 -6
  43. package/src/codegen/generate-i18n-types.test.ts +0 -1137
  44. package/src/codegen/generate-i18n-types.ts +0 -20
  45. package/src/codegen/icu-analysis.ts +0 -110
  46. package/src/codegen/locale-fallback.ts +0 -73
  47. package/src/codegen/locale-policy.test.ts +0 -35
  48. package/src/codegen/locale-policy.ts +0 -30
  49. package/src/codegen/paths.ts +0 -44
  50. package/src/codegen/project-locales-set-namespace.test.ts +0 -134
  51. package/src/codegen/read-dictionary.test.ts +0 -594
  52. package/src/codegen/read-dictionary.ts +0 -260
  53. package/src/codegen/regenerate-namespaces.ts +0 -180
  54. package/src/codegen/run-codegen.test.ts +0 -230
  55. package/src/codegen/run-codegen.ts +0 -252
  56. package/src/codegen/types.ts +0 -10
  57. package/src/codegen/write-file-if-changed.test.ts +0 -42
  58. package/src/codegen/write-file-if-changed.ts +0 -20
  59. package/src/codegen-config/build-config.ts +0 -15
  60. package/src/codegen-config/codegen-config.test.ts +0 -36
  61. package/src/codegen-config/index.ts +0 -37
  62. package/src/codegen-config/type-names.ts +0 -20
  63. package/src/codegen-config/write-config.ts +0 -8
  64. package/src/engine.ts +0 -47
  65. package/src/fetch-artifact.ts +0 -16
  66. package/src/format-core.ts +0 -91
  67. package/src/i18n-handle.ts +0 -216
  68. package/src/icu/extract-variables.test.ts +0 -199
  69. package/src/icu/extract-variables.ts +0 -227
  70. package/src/icu/parse-template.ts +0 -31
  71. package/src/index.ts +0 -63
  72. package/src/load-state.ts +0 -15
  73. package/src/project-locales.test.ts +0 -280
  74. package/src/project-locales.ts +0 -211
  75. package/src/resolve-locale.test.ts +0 -91
  76. package/src/resolve-locale.ts +0 -88
  77. package/src/scope-multi.ts +0 -105
  78. package/src/scope-types.ts +0 -35
  79. package/src/scope.test.ts +0 -172
  80. package/src/serialized-state.test.ts +0 -114
  81. package/src/serialized-state.ts +0 -28
  82. package/src/setup/setup-i18n.test.ts +0 -85
  83. package/src/setup/setup-i18n.ts +0 -156
  84. package/src/types.ts +0 -65
  85. package/src/validation/create-args-schema.ts +0 -76
  86. package/src/validation/create-normalized-schema.ts +0 -52
  87. package/src/validation/errors.ts +0 -43
  88. package/src/validation/index.ts +0 -202
  89. package/src/validation/normalize.ts +0 -299
  90. package/src/validation/to-dictionary.ts +0 -67
  91. package/src/validation/types.ts +0 -82
  92. package/src/validation/validate-normalized.ts +0 -133
  93. package/src/validation/validation.test.ts +0 -562
@@ -1,138 +0,0 @@
1
- import fs from "node:fs";
2
- import type { VariableSpec } from "../icu/extract-variables.js";
3
- import type { DictionarySpec } from "../validation/types.js";
4
- import type { NamespaceEntry } from "./types.js";
5
-
6
- /** Builds a multi-mode {@link DictionarySpec} from ICU analysis for the given entries. */
7
- export function buildDictionarySpecFromAnalysis(
8
- entries: NamespaceEntry[],
9
- argsSpecByNamespace: Record<string, Record<string, VariableSpec>>
10
- ): Extract<DictionarySpec, { mode: "multi" }> {
11
- const requiredKeys: Record<string, readonly string[]> = {};
12
- const argsByKey: Record<string, Record<string, VariableSpec>> = {};
13
-
14
- for (const entry of entries) {
15
- const argsByKeyForNs = argsSpecByNamespace[entry.namespace] ?? {};
16
- requiredKeys[entry.namespace] = Object.keys(argsByKeyForNs).sort();
17
- argsByKey[entry.namespace] = argsByKeyForNs;
18
- }
19
-
20
- return {
21
- mode: "multi",
22
- requiredKeys,
23
- argsByKey,
24
- };
25
- }
26
-
27
- /**
28
- * Loads `DICTIONARY_SPEC` from a previously generated `dictionary-schema.generated.ts`.
29
- * Sync parse of the emitter format from {@link formatDictionarySpecBlock} (no TS runtime import).
30
- */
31
- export function loadDictionarySpecFromSchemaFile(
32
- absoluteSchemaPath: string
33
- ): Extract<DictionarySpec, { mode: "multi" }> {
34
- if (!fs.existsSync(absoluteSchemaPath)) {
35
- throw new Error(
36
- `[Codegen Error] Missing dictionary schema at ${absoluteSchemaPath}. Run runCodegen first.`
37
- );
38
- }
39
-
40
- const source = fs.readFileSync(absoluteSchemaPath, "utf8");
41
- const match = source.match(
42
- /export const DICTIONARY_SPEC = (\{[\s\S]*?\n\}) satisfies DictionarySpec;/
43
- );
44
- if (!match?.[1]) {
45
- throw new Error(
46
- `[Codegen Error] Could not parse DICTIONARY_SPEC from ${absoluteSchemaPath}. Re-run runCodegen.`
47
- );
48
- }
49
-
50
- const cleaned = match[1].replace(/ as const/g, "");
51
- let parsed: unknown;
52
- try {
53
- parsed = new Function(`"use strict"; return (${cleaned});`)();
54
- } catch (error) {
55
- const message = error instanceof Error ? error.message : String(error);
56
- throw new Error(
57
- `[Codegen Error] Failed to evaluate DICTIONARY_SPEC from ${absoluteSchemaPath}: ${message}`
58
- );
59
- }
60
-
61
- if (
62
- !parsed ||
63
- typeof parsed !== "object" ||
64
- (parsed as DictionarySpec).mode !== "multi" ||
65
- !("requiredKeys" in parsed) ||
66
- !("argsByKey" in parsed)
67
- ) {
68
- throw new Error(
69
- `[Codegen Error] DICTIONARY_SPEC in ${absoluteSchemaPath} is not a multi-mode DictionarySpec.`
70
- );
71
- }
72
-
73
- return parsed as Extract<DictionarySpec, { mode: "multi" }>;
74
- }
75
-
76
- function sortedKeys(keys: readonly string[]): string[] {
77
- return [...keys].sort();
78
- }
79
-
80
- function variableSpecsEqual(a: VariableSpec, b: VariableSpec): boolean {
81
- const aKeys = Object.keys(a).sort();
82
- const bKeys = Object.keys(b).sort();
83
- if (aKeys.length !== bKeys.length) {
84
- return false;
85
- }
86
- for (let i = 0; i < aKeys.length; i++) {
87
- const key = aKeys[i]!;
88
- if (key !== bKeys[i] || a[key] !== b[key]) {
89
- return false;
90
- }
91
- }
92
- return true;
93
- }
94
-
95
- function namespaceArgsEqual(
96
- a: Readonly<Record<string, VariableSpec>> | undefined,
97
- b: Readonly<Record<string, VariableSpec>> | undefined
98
- ): boolean {
99
- const aKeys = Object.keys(a ?? {}).sort();
100
- const bKeys = Object.keys(b ?? {}).sort();
101
- if (aKeys.length !== bKeys.length) {
102
- return false;
103
- }
104
- for (const key of aKeys) {
105
- if (!bKeys.includes(key)) {
106
- return false;
107
- }
108
- if (!variableSpecsEqual(a![key]!, b![key]!)) {
109
- return false;
110
- }
111
- }
112
- return true;
113
- }
114
-
115
- /**
116
- * Returns true when `current` matches `established` for every namespace in `namespaces`
117
- * (same keys + same ICU VariableSpec per key).
118
- */
119
- export function namespaceContractsMatch(
120
- namespaces: readonly string[],
121
- current: Extract<DictionarySpec, { mode: "multi" }>,
122
- established: Extract<DictionarySpec, { mode: "multi" }>
123
- ): boolean {
124
- for (const namespace of namespaces) {
125
- const currentKeys = sortedKeys(current.requiredKeys[namespace] ?? []);
126
- const establishedKeys = sortedKeys(established.requiredKeys[namespace] ?? []);
127
- if (
128
- currentKeys.length !== establishedKeys.length ||
129
- currentKeys.some((key, index) => key !== establishedKeys[index])
130
- ) {
131
- return false;
132
- }
133
- if (!namespaceArgsEqual(current.argsByKey[namespace], established.argsByKey[namespace])) {
134
- return false;
135
- }
136
- }
137
- return true;
138
- }
@@ -1,113 +0,0 @@
1
- import type { VariableSpec } from "../../icu/extract-variables.js";
2
- import { GENERATED_FILE_BANNER, toRelativeModuleImport } from "../paths.js";
3
- import type { ImportExtension, NamespaceEntry } from "../types.js";
4
-
5
- function formatVariableSpecObject(spec: VariableSpec): string {
6
- const entries = Object.entries(spec);
7
- if (entries.length === 0) {
8
- return "{}";
9
- }
10
-
11
- const lines = entries.map(
12
- ([name, type]) => ` ${JSON.stringify(name)}: ${JSON.stringify(type)},`
13
- );
14
- return `{\n${lines.join("\n")}\n }`;
15
- }
16
-
17
- /** Builds the `DICTIONARY_SPEC` constant from ICU analysis output (keys + variable args). */
18
- export function formatDictionarySpecBlock(
19
- entries: NamespaceEntry[],
20
- argsSpecByNamespace: Record<string, Record<string, VariableSpec>>
21
- ): string {
22
- const requiredKeysLines = entries
23
- .map((entry) => {
24
- const keys = Object.keys(argsSpecByNamespace[entry.namespace] ?? {})
25
- .map((key) => JSON.stringify(key))
26
- .join(", ");
27
- return ` ${JSON.stringify(entry.namespace)}: [${keys}] as const,`;
28
- })
29
- .join("\n");
30
-
31
- const argsByKeyLines = entries
32
- .map((entry) => {
33
- const argsByKey = argsSpecByNamespace[entry.namespace] ?? {};
34
- const keyLines = Object.entries(argsByKey)
35
- .map(([key, spec]) => ` ${JSON.stringify(key)}: ${formatVariableSpecObject(spec)},`)
36
- .join("\n");
37
- return ` ${JSON.stringify(entry.namespace)}: {\n${keyLines}\n },`;
38
- })
39
- .join("\n");
40
-
41
- return (
42
- `export const DICTIONARY_SPEC = {\n` +
43
- ` mode: 'multi' as const,\n` +
44
- ` requiredKeys: {\n${requiredKeysLines}\n },\n` +
45
- ` argsByKey: {\n${argsByKeyLines}\n },\n` +
46
- `} satisfies DictionarySpec;\n`
47
- );
48
- }
49
-
50
- /**
51
- * Emits the optional dictionary-schema module: `DICTIONARY_SPEC` plus thin wrappers
52
- * around `@xndrjs/i18n/validation` for validating external payloads before
53
- * updating authoring / `regenerateNamespaces` (e.g. CMS → authoring JSON), not for runtime key patches.
54
- */
55
- export function formatDictionarySchemaFile(
56
- schemaTypeName: string,
57
- typesModule: string,
58
- dictionarySpecBlock: string,
59
- importExtension: ImportExtension
60
- ): string {
61
- const typesImport = toRelativeModuleImport(typesModule, importExtension);
62
- const partialImport =
63
- ` validateExternalDictionaryPartial as validateExternalDictionaryPartialCore,\n` +
64
- ` validateExternalNamespacePartial as validateExternalNamespacePartialCore,\n` +
65
- ` validateExternalKey as validateExternalKeyCore,\n`;
66
-
67
- const multiValidators =
68
- `export function validateExternalNamespacePartial<NS extends keyof ${schemaTypeName}>(\n` +
69
- ` namespace: NS,\n` +
70
- ` input: unknown,\n` +
71
- `): ValidationResult<Partial<${schemaTypeName}[NS]>> {\n` +
72
- ` return validateExternalNamespacePartialCore<Partial<${schemaTypeName}[NS]>>(\n` +
73
- ` namespace as string,\n` +
74
- ` input,\n` +
75
- ` DICTIONARY_SPEC,\n` +
76
- ` );\n` +
77
- `}\n\n` +
78
- `export function validateExternalKey<\n` +
79
- ` NS extends keyof ${schemaTypeName},\n` +
80
- ` K extends keyof ${schemaTypeName}[NS],\n` +
81
- `>(\n` +
82
- ` namespace: NS,\n` +
83
- ` key: K,\n` +
84
- ` input: unknown,\n` +
85
- `): ValidationResult<Pick<${schemaTypeName}[NS], K>> {\n` +
86
- ` return validateExternalKeyCore<Pick<${schemaTypeName}[NS], K>>(\n` +
87
- ` namespace as string,\n` +
88
- ` key as string,\n` +
89
- ` input,\n` +
90
- ` DICTIONARY_SPEC,\n` +
91
- ` );\n` +
92
- `}\n\n`;
93
-
94
- return (
95
- `${GENERATED_FILE_BANNER}` +
96
- `import {\n` +
97
- partialImport +
98
- ` type DictionarySpec,\n` +
99
- ` type ValidationResult,\n` +
100
- `} from '@xndrjs/i18n/validation';\n` +
101
- `import type { ${schemaTypeName} } from '${typesImport}';\n\n` +
102
- `${dictionarySpecBlock}\n` +
103
- `export function validateExternalDictionaryPartial(\n` +
104
- ` input: unknown,\n` +
105
- `): ValidationResult<Partial<${schemaTypeName}>> {\n` +
106
- ` return validateExternalDictionaryPartialCore<Partial<${schemaTypeName}>>(\n` +
107
- ` input,\n` +
108
- ` DICTIONARY_SPEC,\n` +
109
- ` );\n` +
110
- `}\n\n` +
111
- multiValidators
112
- );
113
- }
@@ -1,71 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { formatInstanceFile, type InstanceFileOptions } from "./instance-file.js";
3
-
4
- function baseOptions(overrides: Partial<InstanceFileOptions> = {}): InstanceFileOptions {
5
- return {
6
- typesOutputPath: "src/i18n/i18n-types.generated.ts",
7
- namespaceLoadersOutputPath: "src/i18n/namespace-loaders.generated.ts",
8
- paramsTypeName: "AppParams",
9
- schemaTypeName: "AppSchema",
10
- localeTypeName: "AppLocale",
11
- localeFallbackConstName: "LOCALE_FALLBACK",
12
- factoryName: "createI18n",
13
- hasLocaleFallback: true,
14
- hasLocaleType: true,
15
- importExtension: "none",
16
- delivery: "split-by-locale",
17
- ...overrides,
18
- };
19
- }
20
-
21
- function packageImportBlock(output: string): string {
22
- const match = output.match(/import \{([\s\S]*?)\} from '@xndrjs\/i18n'/);
23
- return match?.[1] ?? "";
24
- }
25
-
26
- describe("formatInstanceFile package imports", () => {
27
- it("emits lazy multi handle symbols", () => {
28
- const output = formatInstanceFile(baseOptions());
29
- const imports = packageImportBlock(output);
30
-
31
- expect(imports).toContain("IcuTranslationProviderMulti");
32
- expect(imports).toContain("createI18nHandle");
33
- expect(imports).toContain("type I18nHandle");
34
- expect(imports).toContain("type I18nHandleOptions");
35
- expect(output).toContain("partitionForLocale: (locale) => locale");
36
- expect(output).toContain("I18nHandle<AppSchema, AppParams, AppLocale>");
37
- expect(output).toContain(
38
- "options?: { state?: { dictionary: InitialSchema; resources?: readonly (readonly [string, string])[] }; onMissing?: OnMissingTranslation }"
39
- );
40
- expect(output).toContain("const { state, ...providerOptions } = options ?? {}");
41
- });
42
-
43
- it("injects LOCALE_DELIVERY_AREA for custom delivery", () => {
44
- const output = formatInstanceFile(
45
- baseOptions({
46
- delivery: "custom",
47
- localeDeliveryAreaConstName: "LOCALE_DELIVERY_AREA",
48
- })
49
- );
50
-
51
- expect(output).toContain("LOCALE_DELIVERY_AREA");
52
- expect(output).toContain("partitionForLocale: (locale) => LOCALE_DELIVERY_AREA[locale]");
53
- });
54
-
55
- it("wires createNamespaceLoaders + required fetchImpl in options bag", () => {
56
- const output = formatInstanceFile(
57
- baseOptions({
58
- loaderStrategy: "fetch",
59
- })
60
- );
61
-
62
- expect(output).toContain("type FetchArtifact");
63
- expect(output).toContain("import { createNamespaceLoaders }");
64
- expect(output).toContain(
65
- "options: { fetchImpl: FetchArtifact; state?: { dictionary: InitialSchema; resources?: readonly (readonly [string, string])[] }; onMissing?: OnMissingTranslation }"
66
- );
67
- expect(output).toContain("const { fetchImpl, state, ...providerOptions } = options;");
68
- expect(output).toContain("createNamespaceLoaders(fetchImpl)");
69
- expect(output).not.toContain("import { namespaceLoaders }");
70
- });
71
- });
@@ -1,257 +0,0 @@
1
- import { GENERATED_FILE_BANNER, toModuleBasename, toRelativeModuleImport } from "../paths.js";
2
- import type { DeliveryMode, LoaderStrategy } from "../codegen-config-schema.js";
3
- import type { ImportExtension } from "../types.js";
4
-
5
- /** Emits the generated instance module: `createI18n` factory wrapping the ICU provider. */
6
- export interface InstanceFileOptions {
7
- typesOutputPath: string;
8
- namespaceLoadersOutputPath: string;
9
- paramsTypeName: string;
10
- schemaTypeName: string;
11
- localeTypeName: string;
12
- localeFallbackConstName: string;
13
- factoryName: string;
14
- hasLocaleFallback: boolean;
15
- hasLocaleType: boolean;
16
- importExtension: ImportExtension;
17
- delivery: DeliveryMode;
18
- localeDeliveryAreaConstName?: string;
19
- loaderStrategy?: LoaderStrategy;
20
- }
21
-
22
- const STATE_TYPE = `{ dictionary: InitialSchema; resources?: readonly (readonly [string, string])[] }`;
23
-
24
- export function formatInstanceFile(options: InstanceFileOptions): string {
25
- const {
26
- typesOutputPath,
27
- namespaceLoadersOutputPath,
28
- paramsTypeName,
29
- schemaTypeName,
30
- localeTypeName,
31
- localeFallbackConstName,
32
- factoryName,
33
- hasLocaleFallback,
34
- hasLocaleType,
35
- importExtension,
36
- delivery,
37
- localeDeliveryAreaConstName = "LOCALE_DELIVERY_AREA",
38
- loaderStrategy = "import",
39
- } = options;
40
-
41
- const providerClass = "IcuTranslationProviderMulti";
42
- const typesModule = toModuleBasename(typesOutputPath);
43
- const typesImport = toRelativeModuleImport(typesModule, importExtension);
44
- const loadersModule = toModuleBasename(namespaceLoadersOutputPath);
45
- const loadersImport = toRelativeModuleImport(loadersModule, importExtension);
46
- const schemaTypesImport = `import type { ${paramsTypeName}, ${schemaTypeName}, InitialSchema } from '${typesImport}';\n`;
47
- const typesImportLine = formatTypesValueImport({
48
- hasLocaleType,
49
- hasLocaleFallback,
50
- localeTypeName,
51
- localeFallbackConstName,
52
- localeDeliveryAreaConstName,
53
- delivery,
54
- typesImport,
55
- });
56
- const packageImports = formatPackageImports(providerClass, loaderStrategy);
57
- const loadersImportLine =
58
- loaderStrategy === "fetch"
59
- ? `import { createNamespaceLoaders } from '${loadersImport}';\n`
60
- : `import { namespaceLoaders } from '${loadersImport}';\n`;
61
-
62
- const createFactoryBlock = formatCreateI18nFactory({
63
- providerClass,
64
- schemaTypeName,
65
- paramsTypeName,
66
- localeTypeName,
67
- localeFallbackConstName,
68
- factoryName,
69
- hasLocaleFallback,
70
- delivery,
71
- localeDeliveryAreaConstName,
72
- loaderStrategy,
73
- });
74
-
75
- return (
76
- `${GENERATED_FILE_BANNER}` +
77
- `import {\n` +
78
- ` ${packageImports},\n` +
79
- `} from '@xndrjs/i18n';\n` +
80
- schemaTypesImport +
81
- typesImportLine +
82
- loadersImportLine +
83
- `\n` +
84
- createFactoryBlock
85
- );
86
- }
87
-
88
- function formatTypesValueImport(options: {
89
- hasLocaleType: boolean;
90
- hasLocaleFallback: boolean;
91
- localeTypeName: string;
92
- localeFallbackConstName: string;
93
- localeDeliveryAreaConstName: string;
94
- delivery: DeliveryMode;
95
- typesImport: string;
96
- }): string {
97
- const {
98
- hasLocaleType,
99
- hasLocaleFallback,
100
- localeTypeName,
101
- localeFallbackConstName,
102
- localeDeliveryAreaConstName,
103
- delivery,
104
- typesImport,
105
- } = options;
106
-
107
- if (!hasLocaleType && delivery !== "custom") {
108
- return "";
109
- }
110
-
111
- const valueNames: string[] = [];
112
- if (hasLocaleFallback) {
113
- valueNames.push(localeFallbackConstName);
114
- }
115
- if (delivery === "custom") {
116
- valueNames.push(localeDeliveryAreaConstName);
117
- }
118
-
119
- if (valueNames.length > 0 && hasLocaleType) {
120
- return `import { ${valueNames.join(", ")}, type ${localeTypeName} } from '${typesImport}';\n`;
121
- }
122
- if (valueNames.length > 0) {
123
- return `import { ${valueNames.join(", ")} } from '${typesImport}';\n`;
124
- }
125
- if (hasLocaleType) {
126
- return `import type { ${localeTypeName} } from '${typesImport}';\n`;
127
- }
128
- return "";
129
- }
130
-
131
- function formatCreateI18nFactory(options: {
132
- providerClass: string;
133
- schemaTypeName: string;
134
- paramsTypeName: string;
135
- localeTypeName: string;
136
- localeFallbackConstName: string;
137
- factoryName: string;
138
- hasLocaleFallback: boolean;
139
- delivery: DeliveryMode;
140
- localeDeliveryAreaConstName: string;
141
- loaderStrategy: LoaderStrategy;
142
- }): string {
143
- const {
144
- providerClass,
145
- schemaTypeName,
146
- paramsTypeName,
147
- localeTypeName,
148
- localeFallbackConstName,
149
- factoryName,
150
- hasLocaleFallback,
151
- delivery,
152
- localeDeliveryAreaConstName,
153
- loaderStrategy,
154
- } = options;
155
-
156
- const providerTypeArgs = hasLocaleFallback
157
- ? `${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}, typeof ${localeFallbackConstName}`
158
- : `${schemaTypeName}, ${paramsTypeName}`;
159
- const handleReturnType = `I18nHandle<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
160
- const handleTypeArgs = `<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
161
-
162
- const partitionForLocale =
163
- delivery === "custom"
164
- ? ` partitionForLocale: (locale) => ${localeDeliveryAreaConstName}[locale],\n`
165
- : ` partitionForLocale: (locale) => locale,\n`;
166
-
167
- const optionsType =
168
- loaderStrategy === "fetch"
169
- ? `{ fetchImpl: FetchArtifact; state?: ${STATE_TYPE}; onMissing?: OnMissingTranslation }`
170
- : `{ state?: ${STATE_TYPE}; onMissing?: OnMissingTranslation }`;
171
-
172
- const optionsParam =
173
- loaderStrategy === "fetch" ? ` options: ${optionsType},\n` : ` options?: ${optionsType},\n`;
174
-
175
- const namespaceLoadersLine =
176
- loaderStrategy === "fetch"
177
- ? ` namespaceLoaders: createNamespaceLoaders(fetchImpl),\n`
178
- : ` namespaceLoaders,\n`;
179
-
180
- return (
181
- `export function ${factoryName}(\n` +
182
- optionsParam +
183
- `): ${handleReturnType} {\n` +
184
- formatBody({
185
- providerClass,
186
- providerTypeArgs,
187
- hasLocaleFallback,
188
- localeFallbackConstName,
189
- loaderStrategy,
190
- }) +
191
- ` engine.seedBuilderResources(normalized.resources);\n` +
192
- ` return createI18nHandle${handleTypeArgs}(engine, {\n` +
193
- namespaceLoadersLine +
194
- partitionForLocale +
195
- ` } as I18nHandleOptions<${schemaTypeName}, ${localeTypeName}>);\n` +
196
- `}\n`
197
- );
198
- }
199
-
200
- function formatBody(options: {
201
- providerClass: string;
202
- providerTypeArgs: string;
203
- hasLocaleFallback: boolean;
204
- localeFallbackConstName: string;
205
- loaderStrategy: LoaderStrategy;
206
- }): string {
207
- const {
208
- providerClass,
209
- providerTypeArgs,
210
- hasLocaleFallback,
211
- localeFallbackConstName,
212
- loaderStrategy,
213
- } = options;
214
-
215
- if (loaderStrategy === "fetch") {
216
- const engineBlock = hasLocaleFallback
217
- ? ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, {\n` +
218
- ` localeFallback: ${localeFallbackConstName},\n` +
219
- ` ...providerOptions,\n` +
220
- ` });\n`
221
- : ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, providerOptions);\n`;
222
-
223
- return (
224
- ` const { fetchImpl, state, ...providerOptions } = options;\n` +
225
- ` const normalized = normalizeI18nCreateInput(state);\n` +
226
- engineBlock
227
- );
228
- }
229
-
230
- const engineBlock = hasLocaleFallback
231
- ? ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, {\n` +
232
- ` localeFallback: ${localeFallbackConstName},\n` +
233
- ` ...providerOptions,\n` +
234
- ` });\n`
235
- : ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, providerOptions);\n`;
236
-
237
- return (
238
- ` const { state, ...providerOptions } = options ?? {};\n` +
239
- ` const normalized = normalizeI18nCreateInput(state);\n` +
240
- engineBlock
241
- );
242
- }
243
-
244
- function formatPackageImports(providerClass: string, loaderStrategy: LoaderStrategy): string {
245
- const names = [
246
- providerClass,
247
- "createI18nHandle",
248
- "normalizeI18nCreateInput",
249
- "type I18nHandle",
250
- "type I18nHandleOptions",
251
- "type OnMissingTranslation",
252
- ];
253
- if (loaderStrategy === "fetch") {
254
- names.push("type FetchArtifact");
255
- }
256
- return names.join(",\n ");
257
- }