@gabrielbourget/i18n-foundation 0.1.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.
Files changed (99) hide show
  1. package/README.md +159 -0
  2. package/dist/assets/index.d.ts +35 -0
  3. package/dist/assets/index.d.ts.map +1 -0
  4. package/dist/assets/index.js +102 -0
  5. package/dist/common/index.d.ts +18 -0
  6. package/dist/common/index.d.ts.map +1 -0
  7. package/dist/common/index.js +27 -0
  8. package/dist/core/client.d.ts +22 -0
  9. package/dist/core/client.d.ts.map +1 -0
  10. package/dist/core/client.js +89 -0
  11. package/dist/core/configuration.d.ts +18 -0
  12. package/dist/core/configuration.d.ts.map +1 -0
  13. package/dist/core/configuration.js +36 -0
  14. package/dist/core/createIsolatedI18nInstance.d.ts +13 -0
  15. package/dist/core/createIsolatedI18nInstance.d.ts.map +1 -0
  16. package/dist/core/createIsolatedI18nInstance.js +16 -0
  17. package/dist/core/i18nStrictMode.d.ts +11 -0
  18. package/dist/core/i18nStrictMode.d.ts.map +1 -0
  19. package/dist/core/i18nStrictMode.js +19 -0
  20. package/dist/core/index.d.ts +7 -0
  21. package/dist/core/index.d.ts.map +1 -0
  22. package/dist/core/index.js +6 -0
  23. package/dist/core/initCommon.d.ts +15 -0
  24. package/dist/core/initCommon.d.ts.map +1 -0
  25. package/dist/core/initCommon.js +51 -0
  26. package/dist/core/resourceLoader.d.ts +9 -0
  27. package/dist/core/resourceLoader.d.ts.map +1 -0
  28. package/dist/core/resourceLoader.js +56 -0
  29. package/dist/core/server.d.ts +21 -0
  30. package/dist/core/server.d.ts.map +1 -0
  31. package/dist/core/server.js +20 -0
  32. package/dist/core/types.d.ts +20 -0
  33. package/dist/core/types.d.ts.map +1 -0
  34. package/dist/core/types.js +1 -0
  35. package/dist/env/client.d.ts +29 -0
  36. package/dist/env/client.d.ts.map +1 -0
  37. package/dist/env/client.js +42 -0
  38. package/dist/env/index.d.ts +4 -0
  39. package/dist/env/index.d.ts.map +1 -0
  40. package/dist/env/index.js +3 -0
  41. package/dist/env/server.d.ts +30 -0
  42. package/dist/env/server.d.ts.map +1 -0
  43. package/dist/env/server.js +45 -0
  44. package/dist/env/utils.d.ts +23 -0
  45. package/dist/env/utils.d.ts.map +1 -0
  46. package/dist/env/utils.js +65 -0
  47. package/dist/index.d.ts +6 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +5 -0
  50. package/dist/manifest/index.d.ts +37 -0
  51. package/dist/manifest/index.d.ts.map +1 -0
  52. package/dist/manifest/index.js +46 -0
  53. package/dist/pseudolocalization/index.d.ts +89 -0
  54. package/dist/pseudolocalization/index.d.ts.map +1 -0
  55. package/dist/pseudolocalization/index.js +182 -0
  56. package/dist/verifier/domainContracts.d.ts +43 -0
  57. package/dist/verifier/domainContracts.d.ts.map +1 -0
  58. package/dist/verifier/domainContracts.js +324 -0
  59. package/dist/verifier/fileSystem.d.ts +9 -0
  60. package/dist/verifier/fileSystem.d.ts.map +1 -0
  61. package/dist/verifier/fileSystem.js +42 -0
  62. package/dist/verifier/icuContracts.d.ts +13 -0
  63. package/dist/verifier/icuContracts.d.ts.map +1 -0
  64. package/dist/verifier/icuContracts.js +109 -0
  65. package/dist/verifier/index.d.ts +7 -0
  66. package/dist/verifier/index.d.ts.map +1 -0
  67. package/dist/verifier/index.js +6 -0
  68. package/dist/verifier/keyShape.d.ts +12 -0
  69. package/dist/verifier/keyShape.d.ts.map +1 -0
  70. package/dist/verifier/keyShape.js +73 -0
  71. package/dist/verifier/sheetOrdering.d.ts +2 -0
  72. package/dist/verifier/sheetOrdering.d.ts.map +1 -0
  73. package/dist/verifier/sheetOrdering.js +161 -0
  74. package/dist/verifier/verifyI18nSource.d.ts +25 -0
  75. package/dist/verifier/verifyI18nSource.d.ts.map +1 -0
  76. package/dist/verifier/verifyI18nSource.js +162 -0
  77. package/locales/en/common--carousel.json +30 -0
  78. package/locales/en/common--delete-entity-modal.json +15 -0
  79. package/locales/en/common--file-upload.json +88 -0
  80. package/locales/en/common--form-rules.json +12 -0
  81. package/locales/en/common--form-validation.json +19 -0
  82. package/locales/en/common--pagination.json +36 -0
  83. package/locales/en/common--saved-views.json +37 -0
  84. package/locales/en/common--searching.json +14 -0
  85. package/locales/en/common--sort-and-filter-panel.json +135 -0
  86. package/locales/en/common--theme.json +9 -0
  87. package/locales/en/common--ui.json +52 -0
  88. package/locales/fr/common--carousel.json +30 -0
  89. package/locales/fr/common--delete-entity-modal.json +15 -0
  90. package/locales/fr/common--file-upload.json +88 -0
  91. package/locales/fr/common--form-rules.json +12 -0
  92. package/locales/fr/common--form-validation.json +19 -0
  93. package/locales/fr/common--pagination.json +36 -0
  94. package/locales/fr/common--saved-views.json +37 -0
  95. package/locales/fr/common--searching.json +14 -0
  96. package/locales/fr/common--sort-and-filter-panel.json +135 -0
  97. package/locales/fr/common--theme.json +9 -0
  98. package/locales/fr/common--ui.json +52 -0
  99. package/package.json +103 -0
@@ -0,0 +1,89 @@
1
+ import type { PostProcessorModule } from "i18next";
2
+ export declare const PSEUDOLOCALIZATION_CHARACTER_SET__ACCENTED = "accented";
3
+ export declare const PSEUDOLOCALIZATION_CHARACTER_SET__HIEROGLYPHICS = "hieroglyphics";
4
+ export declare const PSEUDOLOCALIZATION_CHARACTER_SET__WINGDINGS = "wingdings";
5
+ export declare const PSEUDOLOCALIZATION_CHARACTER_SETS: readonly ["accented", "hieroglyphics", "wingdings"];
6
+ export declare const DEFAULT_PSEUDOLOCALIZATION_CHARACTER_SET = "accented";
7
+ export declare const DEFAULT_PSEUDOLOCALIZATION_POST_PROCESSOR_NAME = "i18nFoundationPseudolocalization";
8
+ export type TPseudolocalizationCharacterSet = (typeof PSEUDOLOCALIZATION_CHARACTER_SETS)[number];
9
+ export type TPseudolocalizationReplacementMap = Readonly<Record<string, string>>;
10
+ export type TPseudoLocalizeOptions = {
11
+ characterSet?: TPseudolocalizationCharacterSet | undefined;
12
+ replacementMap?: TPseudolocalizationReplacementMap | undefined;
13
+ replaceDots?: boolean | undefined;
14
+ wrap?: boolean | undefined;
15
+ };
16
+ export type TPseudolocalizationPostProcessorOptions = TPseudoLocalizeOptions & {
17
+ name?: string | undefined;
18
+ };
19
+ export declare const PSEUDOLOCALIZATION_REPLACEMENT_MAPS: {
20
+ readonly accented: {
21
+ a: string;
22
+ b: string;
23
+ c: string;
24
+ d: string;
25
+ e: string;
26
+ f: string;
27
+ g: string;
28
+ h: string;
29
+ i: string;
30
+ j: string;
31
+ k: string;
32
+ l: string;
33
+ m: string;
34
+ n: string;
35
+ o: string;
36
+ p: string;
37
+ q: string;
38
+ r: string;
39
+ s: string;
40
+ t: string;
41
+ u: string;
42
+ v: string;
43
+ w: string;
44
+ x: string;
45
+ y: string;
46
+ z: string;
47
+ A: string;
48
+ B: string;
49
+ C: string;
50
+ D: string;
51
+ E: string;
52
+ F: string;
53
+ G: string;
54
+ H: string;
55
+ I: string;
56
+ J: string;
57
+ K: string;
58
+ L: string;
59
+ M: string;
60
+ N: string;
61
+ O: string;
62
+ P: string;
63
+ Q: string;
64
+ R: string;
65
+ S: string;
66
+ T: string;
67
+ U: string;
68
+ V: string;
69
+ W: string;
70
+ X: string;
71
+ Y: string;
72
+ Z: string;
73
+ };
74
+ readonly hieroglyphics: Readonly<Record<string, string>>;
75
+ readonly wingdings: Readonly<Record<string, string>>;
76
+ };
77
+ export declare const getPseudolocalizationReplacementMap: (characterSet?: TPseudolocalizationCharacterSet) => TPseudolocalizationReplacementMap;
78
+ export declare const buildPseudolocalizationCharacterPattern: (characterSet?: TPseudolocalizationCharacterSet) => RegExp;
79
+ export declare const pseudoLocalize: (input: string, options?: TPseudoLocalizeOptions) => string;
80
+ export declare const createPseudolocalizationPostProcessor: (options?: TPseudolocalizationPostProcessorOptions) => PostProcessorModule;
81
+ export declare const getPseudolocalizationPostProcessConfig: (name?: string) => string;
82
+ declare global {
83
+ var __PSEUDOLOCALIZATION_ACTIVE__: boolean | undefined;
84
+ var __PSEUDOLOCALIZATION_CHARACTER_SET__: TPseudolocalizationCharacterSet | undefined;
85
+ var __PSEUDOLOCALIZE__: ((input: string, options?: TPseudoLocalizeOptions) => string) | undefined;
86
+ var t: ((...args: unknown[]) => unknown) | undefined;
87
+ }
88
+ export declare const initializePseudolocalization: (options?: TPseudoLocalizeOptions) => void;
89
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pseudolocalization/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAElD,eAAO,MAAM,0CAA0C,aAAa,CAAA;AACpE,eAAO,MAAM,+CAA+C,kBAAkB,CAAA;AAC9E,eAAO,MAAM,2CAA2C,cAAc,CAAA;AAEtE,eAAO,MAAM,iCAAiC,qDAIpC,CAAA;AAEV,eAAO,MAAM,wCAAwC,aAA6C,CAAA;AAClG,eAAO,MAAM,8CAA8C,qCAAqC,CAAA;AAEhG,MAAM,MAAM,+BAA+B,GAAG,CAAC,OAAO,iCAAiC,CAAC,CAAC,MAAM,CAAC,CAAA;AAChG,MAAM,MAAM,iCAAiC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,+BAA+B,GAAG,SAAS,CAAA;IAC1D,cAAc,CAAC,EAAE,iCAAiC,GAAG,SAAS,CAAA;IAC9D,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,uCAAuC,GAAG,sBAAsB,GAAG;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,CAAA;AAkID,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAI+C,CAAA;AAE/F,eAAO,MAAM,mCAAmC,GAC9C,eAAc,+BAA0E,KACvF,iCAAsF,CAAA;AAEzF,eAAO,MAAM,uCAAuC,GAClD,eAAc,+BAA0E,KACvF,MAMF,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,UAAS,sBAA2B,KAAG,MASpF,CAAA;AAED,eAAO,MAAM,qCAAqC,GAChD,UAAS,uCAA4C,KACpD,mBAMD,CAAA;AAEF,eAAO,MAAM,sCAAsC,GAAI,aAAqD,KAAG,MACzG,CAAA;AAIN,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,6BAA6B,EAAE,OAAO,GAAG,SAAS,CAAA;IACtD,IAAI,oCAAoC,EAAE,+BAA+B,GAAG,SAAS,CAAA;IACrF,IAAI,kBAAkB,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,KAAK,MAAM,CAAC,GAAG,SAAS,CAAA;IACjG,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CAAA;CACrD;AAED,eAAO,MAAM,4BAA4B,GAAI,UAAS,sBAA2B,KAAG,IAyBnF,CAAA"}
@@ -0,0 +1,182 @@
1
+ export const PSEUDOLOCALIZATION_CHARACTER_SET__ACCENTED = "accented";
2
+ export const PSEUDOLOCALIZATION_CHARACTER_SET__HIEROGLYPHICS = "hieroglyphics";
3
+ export const PSEUDOLOCALIZATION_CHARACTER_SET__WINGDINGS = "wingdings";
4
+ export const PSEUDOLOCALIZATION_CHARACTER_SETS = [
5
+ PSEUDOLOCALIZATION_CHARACTER_SET__ACCENTED,
6
+ PSEUDOLOCALIZATION_CHARACTER_SET__HIEROGLYPHICS,
7
+ PSEUDOLOCALIZATION_CHARACTER_SET__WINGDINGS,
8
+ ];
9
+ export const DEFAULT_PSEUDOLOCALIZATION_CHARACTER_SET = PSEUDOLOCALIZATION_CHARACTER_SET__ACCENTED;
10
+ export const DEFAULT_PSEUDOLOCALIZATION_POST_PROCESSOR_NAME = "i18nFoundationPseudolocalization";
11
+ const accentedMap = {
12
+ a: "ā",
13
+ b: "ƀ",
14
+ c: "ĉ",
15
+ d: "đ",
16
+ e: "ē",
17
+ f: "ƒ",
18
+ g: "ĝ",
19
+ h: "ĥ",
20
+ i: "ī",
21
+ j: "ĵ",
22
+ k: "ķ",
23
+ l: "ĺ",
24
+ m: "ḿ",
25
+ n: "ń",
26
+ o: "ō",
27
+ p: "ṕ",
28
+ q: "ʠ",
29
+ r: "ŕ",
30
+ s: "ś",
31
+ t: "ť",
32
+ u: "ū",
33
+ v: "ṽ",
34
+ w: "ŵ",
35
+ x: "ẋ",
36
+ y: "ȳ",
37
+ z: "ž",
38
+ A: "Ā",
39
+ B: "Ƀ",
40
+ C: "Ċ",
41
+ D: "Ď",
42
+ E: "Ē",
43
+ F: "Ƒ",
44
+ G: "Ĝ",
45
+ H: "Ĥ",
46
+ I: "Ī",
47
+ J: "Ĵ",
48
+ K: "Ķ",
49
+ L: "Ļ",
50
+ M: "Ḿ",
51
+ N: "Ń",
52
+ O: "Ō",
53
+ P: "Ṕ",
54
+ Q: "Ɋ",
55
+ R: "Ŕ",
56
+ S: "Ś",
57
+ T: "Ť",
58
+ U: "Ū",
59
+ V: "Ṽ",
60
+ W: "Ŵ",
61
+ X: "Ẋ",
62
+ Y: "Ȳ",
63
+ Z: "Ž",
64
+ };
65
+ const hieroglyphicsLowercase = [
66
+ "𓄿",
67
+ "𓃀",
68
+ "𓍿",
69
+ "𓂧",
70
+ "𓇋",
71
+ "𓆑",
72
+ "𓎼",
73
+ "𓉔",
74
+ "𓇋",
75
+ "𓆓",
76
+ "𓎡",
77
+ "𓃭",
78
+ "𓅓",
79
+ "𓈖",
80
+ "𓍯",
81
+ "𓊪",
82
+ "𓏘",
83
+ "𓂋",
84
+ "𓋴",
85
+ "𓏏",
86
+ "𓅱",
87
+ "𓆑",
88
+ "𓅱",
89
+ "𓐍",
90
+ "𓇌",
91
+ "𓊃",
92
+ ];
93
+ const wingdingsLowercase = [
94
+ "✈",
95
+ "☞",
96
+ "☾",
97
+ "◆",
98
+ "☰",
99
+ "✿",
100
+ "●",
101
+ "♥",
102
+ "♧",
103
+ "♪",
104
+ "☼",
105
+ "◈",
106
+ "✉",
107
+ "✦",
108
+ "○",
109
+ "□",
110
+ "❖",
111
+ "☛",
112
+ "✶",
113
+ "✝",
114
+ "☂",
115
+ "✓",
116
+ "⌘",
117
+ "✕",
118
+ "☯",
119
+ "✹",
120
+ ];
121
+ const buildAlphabetMap = (lowercase) => {
122
+ const map = {};
123
+ for (let index = 0; index < 26; index += 1) {
124
+ const lowercaseLetter = String.fromCharCode("a".charCodeAt(0) + index);
125
+ const uppercaseLetter = lowercaseLetter.toUpperCase();
126
+ const replacement = lowercase[index] ?? lowercaseLetter;
127
+ map[lowercaseLetter] = replacement;
128
+ map[uppercaseLetter] = replacement;
129
+ }
130
+ return map;
131
+ };
132
+ export const PSEUDOLOCALIZATION_REPLACEMENT_MAPS = {
133
+ [PSEUDOLOCALIZATION_CHARACTER_SET__ACCENTED]: accentedMap,
134
+ [PSEUDOLOCALIZATION_CHARACTER_SET__HIEROGLYPHICS]: buildAlphabetMap(hieroglyphicsLowercase),
135
+ [PSEUDOLOCALIZATION_CHARACTER_SET__WINGDINGS]: buildAlphabetMap(wingdingsLowercase),
136
+ };
137
+ export const getPseudolocalizationReplacementMap = (characterSet = DEFAULT_PSEUDOLOCALIZATION_CHARACTER_SET) => PSEUDOLOCALIZATION_REPLACEMENT_MAPS[characterSet];
138
+ export const buildPseudolocalizationCharacterPattern = (characterSet = DEFAULT_PSEUDOLOCALIZATION_CHARACTER_SET) => {
139
+ const characters = [
140
+ ...new Set(Object.values(getPseudolocalizationReplacementMap(characterSet)).flatMap((value) => [...value])),
141
+ ];
142
+ return new RegExp(`[${characters.map((character) => character.replace(/[\\\]^]/gu, "\\$&")).join("")}]`, "u");
143
+ };
144
+ export const pseudoLocalize = (input, options = {}) => {
145
+ const replacementMap = options.replacementMap ?? getPseudolocalizationReplacementMap(options.characterSet);
146
+ const replaceDots = options.replaceDots ?? true;
147
+ const wrap = options.wrap ?? true;
148
+ const transformed = input
149
+ .replace(/[A-Za-z]/gu, (character) => replacementMap[character] ?? character)
150
+ .replace(/\./gu, replaceDots ? "•" : ".");
151
+ return wrap ? `[${transformed}]` : transformed;
152
+ };
153
+ export const createPseudolocalizationPostProcessor = (options = {}) => ({
154
+ name: options.name ?? DEFAULT_PSEUDOLOCALIZATION_POST_PROCESSOR_NAME,
155
+ type: "postProcessor",
156
+ process(value) {
157
+ return pseudoLocalize(value, options);
158
+ },
159
+ });
160
+ export const getPseudolocalizationPostProcessConfig = (name = DEFAULT_PSEUDOLOCALIZATION_POST_PROCESSOR_NAME) => name;
161
+ let installed = false;
162
+ export const initializePseudolocalization = (options = {}) => {
163
+ if (installed)
164
+ return;
165
+ installed = true;
166
+ const characterSet = options.characterSet ?? DEFAULT_PSEUDOLOCALIZATION_CHARACTER_SET;
167
+ globalThis.__PSEUDOLOCALIZATION_ACTIVE__ = true;
168
+ globalThis.__PSEUDOLOCALIZATION_CHARACTER_SET__ = characterSet;
169
+ globalThis.__PSEUDOLOCALIZE__ = pseudoLocalize;
170
+ const maybeT = globalThis.t;
171
+ if (typeof maybeT === "function") {
172
+ const safeT = maybeT;
173
+ const wrappedT = (...args) => {
174
+ const result = safeT(...args);
175
+ return typeof result === "string" ? pseudoLocalize(result, options) : String(result);
176
+ };
177
+ globalThis.t = wrappedT;
178
+ }
179
+ if (typeof console !== "undefined" && typeof console.info === "function") {
180
+ console.info(`%c[i18n] Pseudolocalization active (${characterSet})`, "color:#00aaff;font-weight:bold;");
181
+ }
182
+ };
@@ -0,0 +1,43 @@
1
+ export type TStringArrayExportContract = {
2
+ label: string;
3
+ sourceFile: string;
4
+ sourceExport: string;
5
+ };
6
+ export type TDomainTranslationContract = TStringArrayExportContract & {
7
+ namespace: string;
8
+ keyPrefix: string;
9
+ excludedCodes?: readonly string[] | undefined;
10
+ requiredKeySuffixes?: readonly string[] | undefined;
11
+ };
12
+ export type TDomainSeedSourceContract = TStringArrayExportContract & {
13
+ seedFile: string;
14
+ sourceModule: string;
15
+ };
16
+ export type TParsedStringArrayExport = {
17
+ errors: readonly string[];
18
+ values: readonly string[];
19
+ };
20
+ export type TVerifyDomainTranslationContractsArgs = {
21
+ contracts: readonly TDomainTranslationContract[];
22
+ locales: readonly string[];
23
+ repoRoot: string;
24
+ sheetsByLocale: ReadonlyMap<string, ReadonlyMap<string, unknown>>;
25
+ };
26
+ export type TVerifyDomainSeedSourceContractsArgs = {
27
+ contracts: readonly TDomainSeedSourceContract[];
28
+ repoRoot: string;
29
+ };
30
+ export type TVerifyDomainTranslationContractsResult = {
31
+ checkedCodeCount: number;
32
+ checkedContractCount: number;
33
+ errors: readonly string[];
34
+ };
35
+ export type TVerifyDomainSeedSourceContractsResult = {
36
+ checkedContractCount: number;
37
+ checkedSourceCodeCount: number;
38
+ errors: readonly string[];
39
+ };
40
+ export declare const parseStringArrayExport: (repoRoot: string, contract: TStringArrayExportContract, sourceArrayCache?: Map<string, TParsedStringArrayExport>) => Promise<TParsedStringArrayExport>;
41
+ export declare const verifyDomainTranslationContracts: ({ contracts, locales, repoRoot, sheetsByLocale, }: TVerifyDomainTranslationContractsArgs) => Promise<TVerifyDomainTranslationContractsResult>;
42
+ export declare const verifyDomainSeedSourceContracts: ({ contracts, repoRoot, }: TVerifyDomainSeedSourceContractsArgs) => Promise<TVerifyDomainSeedSourceContractsResult>;
43
+ //# sourceMappingURL=domainContracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domainContracts.d.ts","sourceRoot":"","sources":["../../src/verifier/domainContracts.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG,0BAA0B,GAAG;IACpE,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAC7C,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CACpD,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG,0BAA0B,GAAG;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,SAAS,EAAE,SAAS,0BAA0B,EAAE,CAAA;IAChD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClE,CAAA;AAED,MAAM,MAAM,oCAAoC,GAAG;IACjD,SAAS,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC/C,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,uCAAuC,GAAG;IACpD,gBAAgB,EAAE,MAAM,CAAA;IACxB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,sCAAsC,GAAG;IACnD,oBAAoB,EAAE,MAAM,CAAA;IAC5B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAuGD,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,UAAU,0BAA0B,EACpC,mBAAkB,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAa,KAClE,OAAO,CAAC,wBAAwB,CAkFlC,CAAA;AAwHD,eAAO,MAAM,gCAAgC,GAAU,mDAKpD,qCAAqC,KAAG,OAAO,CAAC,uCAAuC,CAyFzF,CAAA;AAED,eAAO,MAAM,+BAA+B,GAAU,0BAGnD,oCAAoC,KAAG,OAAO,CAAC,sCAAsC,CAiDvF,CAAA"}
@@ -0,0 +1,324 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import * as ts from "typescript";
4
+ import { isPlainObject } from "./keyShape.js";
5
+ const DOMAIN_CODE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+ const isStringLiteralLike = (node) => ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);
7
+ const unwrapExpression = (expression) => {
8
+ let currentExpression = expression;
9
+ while (ts.isAsExpression(currentExpression) ||
10
+ ts.isTypeAssertionExpression(currentExpression) ||
11
+ ts.isParenthesizedExpression(currentExpression)) {
12
+ currentExpression = currentExpression.expression;
13
+ }
14
+ return currentExpression;
15
+ };
16
+ const collectTopLevelConstDeclarations = (sourceFile) => {
17
+ const declarations = new Map();
18
+ for (const statement of sourceFile.statements) {
19
+ if (!ts.isVariableStatement(statement))
20
+ continue;
21
+ const isConst = Boolean(statement.declarationList.flags & ts.NodeFlags.Const);
22
+ const isExported = statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
23
+ for (const declaration of statement.declarationList.declarations) {
24
+ if (!ts.isIdentifier(declaration.name))
25
+ continue;
26
+ declarations.set(declaration.name.text, {
27
+ initializer: declaration.initializer,
28
+ isConst,
29
+ isExported,
30
+ });
31
+ }
32
+ }
33
+ return declarations;
34
+ };
35
+ const resolveStringExpression = (expression, declarations, context, resolutionStack = []) => {
36
+ if (!expression) {
37
+ return {
38
+ errors: [`${context} has no initializer`],
39
+ value: null,
40
+ };
41
+ }
42
+ const unwrappedExpression = unwrapExpression(expression);
43
+ if (isStringLiteralLike(unwrappedExpression)) {
44
+ return {
45
+ errors: [],
46
+ value: unwrappedExpression.text,
47
+ };
48
+ }
49
+ if (ts.isIdentifier(unwrappedExpression)) {
50
+ const identifier = unwrappedExpression.text;
51
+ if (resolutionStack.includes(identifier)) {
52
+ return {
53
+ errors: [`${context} has circular constant reference: ${[...resolutionStack, identifier].join(" -> ")}`],
54
+ value: null,
55
+ };
56
+ }
57
+ const declaration = declarations.get(identifier);
58
+ if (!declaration) {
59
+ return {
60
+ errors: [`${context} references unknown constant ${identifier}`],
61
+ value: null,
62
+ };
63
+ }
64
+ return resolveStringExpression(declaration.initializer, declarations, context, [...resolutionStack, identifier]);
65
+ }
66
+ return {
67
+ errors: [`${context} uses unsupported array element syntax: ${unwrappedExpression.getText()}`],
68
+ value: null,
69
+ };
70
+ };
71
+ export const parseStringArrayExport = async (repoRoot, contract, sourceArrayCache = new Map()) => {
72
+ const cacheKey = `${contract.sourceFile}#${contract.sourceExport}`;
73
+ const cachedSourceArray = sourceArrayCache.get(cacheKey);
74
+ if (cachedSourceArray)
75
+ return cachedSourceArray;
76
+ const sourcePath = path.join(repoRoot, contract.sourceFile);
77
+ const sourceText = await fs.readFile(sourcePath, "utf8");
78
+ const sourceFile = ts.createSourceFile(sourcePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
79
+ const declarations = collectTopLevelConstDeclarations(sourceFile);
80
+ const declaration = declarations.get(contract.sourceExport);
81
+ const context = `${contract.sourceFile} ${contract.sourceExport}`;
82
+ const errors = [];
83
+ if (!declaration) {
84
+ const result = {
85
+ errors: [`${context} was not found`],
86
+ values: [],
87
+ };
88
+ sourceArrayCache.set(cacheKey, result);
89
+ return result;
90
+ }
91
+ if (!declaration.isConst) {
92
+ errors.push(`${context} must be declared with const`);
93
+ }
94
+ if (!declaration.isExported) {
95
+ errors.push(`${context} must be exported`);
96
+ }
97
+ if (!declaration.initializer) {
98
+ const result = {
99
+ errors: [...errors, `${context} has no initializer`],
100
+ values: [],
101
+ };
102
+ sourceArrayCache.set(cacheKey, result);
103
+ return result;
104
+ }
105
+ const arrayExpression = unwrapExpression(declaration.initializer);
106
+ if (!ts.isArrayLiteralExpression(arrayExpression)) {
107
+ const result = {
108
+ errors: [...errors, `${context} must be an array literal`],
109
+ values: [],
110
+ };
111
+ sourceArrayCache.set(cacheKey, result);
112
+ return result;
113
+ }
114
+ const values = [];
115
+ for (const element of arrayExpression.elements) {
116
+ if (ts.isSpreadElement(element)) {
117
+ errors.push(`${context} uses unsupported spread syntax in its domain code array`);
118
+ continue;
119
+ }
120
+ const resolvedElement = resolveStringExpression(element, declarations, context);
121
+ errors.push(...resolvedElement.errors);
122
+ if (resolvedElement.value != null) {
123
+ values.push(resolvedElement.value);
124
+ }
125
+ }
126
+ const result = {
127
+ errors,
128
+ values,
129
+ };
130
+ sourceArrayCache.set(cacheKey, result);
131
+ return result;
132
+ };
133
+ const getLeafKeyPaths = (value, pathSegments = []) => {
134
+ if (!isPlainObject(value))
135
+ return [pathSegments.join(".")];
136
+ return Object.entries(value).flatMap(([key, child]) => {
137
+ if (pathSegments.length === 0 && key === "_meta")
138
+ return [];
139
+ return getLeafKeyPaths(child, [...pathSegments, key]);
140
+ });
141
+ };
142
+ const getExpectedTranslationKey = (contract, code, requiredKeySuffix) => requiredKeySuffix ? `${contract.keyPrefix}.${code}.${requiredKeySuffix}` : `${contract.keyPrefix}.${code}`;
143
+ const collectRuntimeNamedImportLocalNames = (sourceFile, sourceModule, importedName) => {
144
+ const localNames = [];
145
+ let hasTypeOnlyMatch = false;
146
+ for (const statement of sourceFile.statements) {
147
+ if (!ts.isImportDeclaration(statement))
148
+ continue;
149
+ if (!ts.isStringLiteral(statement.moduleSpecifier))
150
+ continue;
151
+ if (statement.moduleSpecifier.text !== sourceModule)
152
+ continue;
153
+ const importClause = statement.importClause;
154
+ const namedBindings = importClause?.namedBindings;
155
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
156
+ continue;
157
+ for (const element of namedBindings.elements) {
158
+ const exportedName = element.propertyName?.text ?? element.name.text;
159
+ if (exportedName !== importedName)
160
+ continue;
161
+ if (importClause.isTypeOnly || element.isTypeOnly) {
162
+ hasTypeOnlyMatch = true;
163
+ continue;
164
+ }
165
+ localNames.push(element.name.text);
166
+ }
167
+ }
168
+ return {
169
+ hasTypeOnlyMatch,
170
+ localNames,
171
+ };
172
+ };
173
+ const sourceUsesArrayMapCall = (sourceFile, arrayIdentifier) => {
174
+ let hasMapCall = false;
175
+ const visit = (node) => {
176
+ if (hasMapCall)
177
+ return;
178
+ if (ts.isCallExpression(node)) {
179
+ const expression = unwrapExpression(node.expression);
180
+ if (ts.isPropertyAccessExpression(expression) && expression.name.text === "map") {
181
+ const mapTarget = unwrapExpression(expression.expression);
182
+ if (ts.isIdentifier(mapTarget) && mapTarget.text === arrayIdentifier) {
183
+ hasMapCall = true;
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ ts.forEachChild(node, visit);
189
+ };
190
+ visit(sourceFile);
191
+ return hasMapCall;
192
+ };
193
+ const getContractKeyDetails = (contract, keyPath) => {
194
+ const prefix = `${contract.keyPrefix}.`;
195
+ if (!keyPath.startsWith(prefix))
196
+ return null;
197
+ const remainder = keyPath.slice(prefix.length);
198
+ const requiredKeySuffixes = contract.requiredKeySuffixes ?? [""];
199
+ for (const suffix of requiredKeySuffixes) {
200
+ if (!suffix && !remainder.includes(".")) {
201
+ return {
202
+ code: remainder,
203
+ expectedShape: true,
204
+ };
205
+ }
206
+ if (suffix && remainder.endsWith(`.${suffix}`)) {
207
+ return {
208
+ code: remainder.slice(0, -(suffix.length + 1)),
209
+ expectedShape: true,
210
+ };
211
+ }
212
+ }
213
+ return {
214
+ code: remainder,
215
+ expectedShape: false,
216
+ };
217
+ };
218
+ export const verifyDomainTranslationContracts = async ({ contracts, locales, repoRoot, sheetsByLocale, }) => {
219
+ const errors = [];
220
+ const sourceArrayCache = new Map();
221
+ let checkedCodeCount = 0;
222
+ for (const contract of contracts) {
223
+ const sourceArray = await parseStringArrayExport(repoRoot, contract, sourceArrayCache);
224
+ const allSourceCodes = sourceArray.values;
225
+ const excludedCodeSet = new Set(contract.excludedCodes ?? []);
226
+ const sourceCodes = allSourceCodes.filter((code) => !excludedCodeSet.has(code));
227
+ const allSourceCodeSet = new Set(allSourceCodes);
228
+ const sourceCodeSet = new Set(sourceCodes);
229
+ const requiredKeySuffixes = contract.requiredKeySuffixes ?? [""];
230
+ const sourceContext = `${contract.label} (${contract.sourceFile} ${contract.sourceExport})`;
231
+ errors.push(...sourceArray.errors.map((error) => `${contract.label}: ${error}`));
232
+ if (allSourceCodes.length === 0)
233
+ continue;
234
+ for (const code of allSourceCodes) {
235
+ if (!DOMAIN_CODE_PATTERN.test(code)) {
236
+ errors.push(`${sourceContext} contains a non-kebab-case domain code: ${code}`);
237
+ }
238
+ }
239
+ const duplicateCodes = allSourceCodes.filter((code, index) => allSourceCodes.indexOf(code) !== index);
240
+ for (const code of [...new Set(duplicateCodes)]) {
241
+ errors.push(`${sourceContext} contains duplicate domain code: ${code}`);
242
+ }
243
+ for (const code of excludedCodeSet) {
244
+ if (!allSourceCodeSet.has(code)) {
245
+ errors.push(`${sourceContext} excludes unknown domain code: ${code}`);
246
+ }
247
+ }
248
+ if (sourceCodes.length === 0)
249
+ continue;
250
+ checkedCodeCount += sourceCodes.length;
251
+ for (const locale of locales) {
252
+ const sheet = sheetsByLocale.get(locale)?.get(contract.namespace);
253
+ const sheetContext = `${locale}/${contract.namespace}.json`;
254
+ if (!sheet) {
255
+ errors.push(`${contract.label}: ${sheetContext} was not found`);
256
+ continue;
257
+ }
258
+ const leafKeyPaths = getLeafKeyPaths(sheet);
259
+ const leafKeyPathSet = new Set(leafKeyPaths);
260
+ const contractKeyDetails = leafKeyPaths
261
+ .map((keyPath) => ({
262
+ details: getContractKeyDetails(contract, keyPath),
263
+ keyPath,
264
+ }))
265
+ .filter((entry) => Boolean(entry.details));
266
+ for (const code of sourceCodes) {
267
+ for (const requiredKeySuffix of requiredKeySuffixes) {
268
+ const expectedKey = getExpectedTranslationKey(contract, code, requiredKeySuffix);
269
+ if (!leafKeyPathSet.has(expectedKey)) {
270
+ errors.push(`${contract.label}: ${sheetContext} is missing ${expectedKey}`);
271
+ }
272
+ }
273
+ }
274
+ for (const { keyPath, details } of contractKeyDetails) {
275
+ if (!details.expectedShape) {
276
+ errors.push(`${contract.label}: ${sheetContext} has unexpected key shape ${keyPath}`);
277
+ continue;
278
+ }
279
+ if (!sourceCodeSet.has(details.code)) {
280
+ errors.push(`${contract.label}: ${sheetContext} has stale or unknown code key ${keyPath}`);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ return {
286
+ checkedCodeCount,
287
+ checkedContractCount: contracts.length,
288
+ errors,
289
+ };
290
+ };
291
+ export const verifyDomainSeedSourceContracts = async ({ contracts, repoRoot, }) => {
292
+ const errors = [];
293
+ const sourceArrayCache = new Map();
294
+ let checkedSourceCodeCount = 0;
295
+ for (const contract of contracts) {
296
+ const sourceArray = await parseStringArrayExport(repoRoot, contract, sourceArrayCache);
297
+ const seedPath = path.join(repoRoot, contract.seedFile);
298
+ const seedText = await fs.readFile(seedPath, "utf8");
299
+ const seedSourceFile = ts.createSourceFile(seedPath, seedText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
300
+ const sourceContext = `${contract.label} (${contract.sourceFile} ${contract.sourceExport})`;
301
+ const seedContext = `${contract.label}: ${contract.seedFile}`;
302
+ errors.push(...sourceArray.errors.map((error) => `${contract.label}: ${error}`));
303
+ if (sourceArray.values.length > 0) {
304
+ checkedSourceCodeCount += sourceArray.values.length;
305
+ }
306
+ const { hasTypeOnlyMatch, localNames } = collectRuntimeNamedImportLocalNames(seedSourceFile, contract.sourceModule, contract.sourceExport);
307
+ if (localNames.length === 0) {
308
+ errors.push(`${seedContext} must import ${contract.sourceExport} from ${contract.sourceModule}`);
309
+ if (hasTypeOnlyMatch) {
310
+ errors.push(`${seedContext} imports ${contract.sourceExport} as type-only, but seed rows need the runtime array`);
311
+ }
312
+ continue;
313
+ }
314
+ const mapsOverSourceArray = localNames.some((localName) => sourceUsesArrayMapCall(seedSourceFile, localName));
315
+ if (!mapsOverSourceArray) {
316
+ errors.push(`${seedContext} must map over ${contract.sourceExport} when building seed rows from ${sourceContext}`);
317
+ }
318
+ }
319
+ return {
320
+ checkedContractCount: contracts.length,
321
+ checkedSourceCodeCount,
322
+ errors,
323
+ };
324
+ };
@@ -0,0 +1,9 @@
1
+ export type TReadJSONFileResult = {
2
+ json: unknown;
3
+ raw: string;
4
+ };
5
+ export declare const listDirNames: (directoryPath: string) => Promise<string[]>;
6
+ export declare const listFilesRecursive: (directoryPath: string) => Promise<string[]>;
7
+ export declare const listJSONFiles: (directoryPath: string) => Promise<string[]>;
8
+ export declare const readJSONFileOrThrow: (filePath: string, errorPrefix?: string) => Promise<TReadJSONFileResult>;
9
+ //# sourceMappingURL=fileSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSystem.d.ts","sourceRoot":"","sources":["../../src/verifier/fileSystem.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,eAAO,MAAM,YAAY,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAO1E,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAkBhF,CAAA;AAED,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAO3E,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,EAChB,oBAA6B,KAC5B,OAAO,CAAC,mBAAmB,CAW7B,CAAA"}