@langapi/mcp-server 1.0.7 → 1.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 (56) hide show
  1. package/dist/handlers/index.d.ts +7 -0
  2. package/dist/handlers/index.d.ts.map +1 -0
  3. package/dist/handlers/index.js +7 -0
  4. package/dist/handlers/index.js.map +1 -0
  5. package/dist/handlers/xcstrings-sync-handler.d.ts +72 -0
  6. package/dist/handlers/xcstrings-sync-handler.d.ts.map +1 -0
  7. package/dist/handlers/xcstrings-sync-handler.js +126 -0
  8. package/dist/handlers/xcstrings-sync-handler.js.map +1 -0
  9. package/dist/locale-detection/index.d.ts.map +1 -1
  10. package/dist/locale-detection/index.js +68 -4
  11. package/dist/locale-detection/index.js.map +1 -1
  12. package/dist/locale-detection/patterns.d.ts.map +1 -1
  13. package/dist/locale-detection/patterns.js +30 -0
  14. package/dist/locale-detection/patterns.js.map +1 -1
  15. package/dist/tools/get-translation-status.d.ts.map +1 -1
  16. package/dist/tools/get-translation-status.js +34 -6
  17. package/dist/tools/get-translation-status.js.map +1 -1
  18. package/dist/tools/list-local-locales.d.ts +1 -1
  19. package/dist/tools/list-local-locales.js +2 -2
  20. package/dist/tools/list-local-locales.js.map +1 -1
  21. package/dist/tools/sync-translations.d.ts.map +1 -1
  22. package/dist/tools/sync-translations.js +199 -40
  23. package/dist/tools/sync-translations.js.map +1 -1
  24. package/dist/utils/apple-common.d.ts +63 -0
  25. package/dist/utils/apple-common.d.ts.map +1 -0
  26. package/dist/utils/apple-common.js +118 -0
  27. package/dist/utils/apple-common.js.map +1 -0
  28. package/dist/utils/apple-common.test.d.ts +2 -0
  29. package/dist/utils/apple-common.test.d.ts.map +1 -0
  30. package/dist/utils/apple-common.test.js +108 -0
  31. package/dist/utils/apple-common.test.js.map +1 -0
  32. package/dist/utils/strings-parser.d.ts +62 -0
  33. package/dist/utils/strings-parser.d.ts.map +1 -0
  34. package/dist/utils/strings-parser.js +276 -0
  35. package/dist/utils/strings-parser.js.map +1 -0
  36. package/dist/utils/strings-parser.test.d.ts +2 -0
  37. package/dist/utils/strings-parser.test.d.ts.map +1 -0
  38. package/dist/utils/strings-parser.test.js +143 -0
  39. package/dist/utils/strings-parser.test.js.map +1 -0
  40. package/dist/utils/stringsdict-parser.d.ts +106 -0
  41. package/dist/utils/stringsdict-parser.d.ts.map +1 -0
  42. package/dist/utils/stringsdict-parser.js +473 -0
  43. package/dist/utils/stringsdict-parser.js.map +1 -0
  44. package/dist/utils/stringsdict-parser.test.d.ts +2 -0
  45. package/dist/utils/stringsdict-parser.test.d.ts.map +1 -0
  46. package/dist/utils/stringsdict-parser.test.js +244 -0
  47. package/dist/utils/stringsdict-parser.test.js.map +1 -0
  48. package/dist/utils/xcstrings-parser.d.ts +128 -0
  49. package/dist/utils/xcstrings-parser.d.ts.map +1 -0
  50. package/dist/utils/xcstrings-parser.js +216 -0
  51. package/dist/utils/xcstrings-parser.js.map +1 -0
  52. package/dist/utils/xcstrings-parser.test.d.ts +2 -0
  53. package/dist/utils/xcstrings-parser.test.d.ts.map +1 -0
  54. package/dist/utils/xcstrings-parser.test.js +157 -0
  55. package/dist/utils/xcstrings-parser.test.js.map +1 -0
  56. package/package.json +1 -1
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isStringsFile, isXCStringsFile, isStringsDictFile, detectAppleFileType, isAppleLocalizationFile, extractLanguageFromLproj, computeAppleLprojTargetPath, } from "./apple-common.js";
3
+ describe("Apple Common Utilities", () => {
4
+ describe("isStringsFile", () => {
5
+ it("should return true for .strings files", () => {
6
+ expect(isStringsFile("Localizable.strings")).toBe(true);
7
+ expect(isStringsFile("/path/to/en.lproj/Localizable.strings")).toBe(true);
8
+ });
9
+ it("should be case-insensitive", () => {
10
+ expect(isStringsFile("Localizable.STRINGS")).toBe(true);
11
+ expect(isStringsFile("file.Strings")).toBe(true);
12
+ });
13
+ it("should return false for other files", () => {
14
+ expect(isStringsFile("file.json")).toBe(false);
15
+ expect(isStringsFile("file.xcstrings")).toBe(false);
16
+ });
17
+ });
18
+ describe("isXCStringsFile", () => {
19
+ it("should return true for .xcstrings files", () => {
20
+ expect(isXCStringsFile("Localizable.xcstrings")).toBe(true);
21
+ expect(isXCStringsFile("/path/to/Localizable.xcstrings")).toBe(true);
22
+ });
23
+ it("should be case-insensitive", () => {
24
+ expect(isXCStringsFile("File.XCSTRINGS")).toBe(true);
25
+ });
26
+ it("should return false for other files", () => {
27
+ expect(isXCStringsFile("file.strings")).toBe(false);
28
+ expect(isXCStringsFile("file.json")).toBe(false);
29
+ });
30
+ });
31
+ describe("isStringsDictFile", () => {
32
+ it("should return true for .stringsdict files", () => {
33
+ expect(isStringsDictFile("Localizable.stringsdict")).toBe(true);
34
+ expect(isStringsDictFile("/path/to/en.lproj/Localizable.stringsdict")).toBe(true);
35
+ });
36
+ it("should be case-insensitive", () => {
37
+ expect(isStringsDictFile("File.STRINGSDICT")).toBe(true);
38
+ });
39
+ it("should return false for other files", () => {
40
+ expect(isStringsDictFile("file.strings")).toBe(false);
41
+ expect(isStringsDictFile("file.json")).toBe(false);
42
+ });
43
+ });
44
+ describe("detectAppleFileType", () => {
45
+ it("should detect .strings files", () => {
46
+ expect(detectAppleFileType("Localizable.strings")).toBe("strings");
47
+ });
48
+ it("should detect .xcstrings files", () => {
49
+ expect(detectAppleFileType("Localizable.xcstrings")).toBe("xcstrings");
50
+ });
51
+ it("should detect .stringsdict files", () => {
52
+ expect(detectAppleFileType("Localizable.stringsdict")).toBe("stringsdict");
53
+ });
54
+ it("should return null for non-Apple files", () => {
55
+ expect(detectAppleFileType("file.json")).toBeNull();
56
+ expect(detectAppleFileType("file.arb")).toBeNull();
57
+ });
58
+ });
59
+ describe("isAppleLocalizationFile", () => {
60
+ it("should return true for Apple files", () => {
61
+ expect(isAppleLocalizationFile("file.strings")).toBe(true);
62
+ expect(isAppleLocalizationFile("file.xcstrings")).toBe(true);
63
+ expect(isAppleLocalizationFile("file.stringsdict")).toBe(true);
64
+ });
65
+ it("should return false for non-Apple files", () => {
66
+ expect(isAppleLocalizationFile("file.json")).toBe(false);
67
+ expect(isAppleLocalizationFile("file.arb")).toBe(false);
68
+ });
69
+ });
70
+ describe("extractLanguageFromLproj", () => {
71
+ it("should extract language from en.lproj path", () => {
72
+ expect(extractLanguageFromLproj("/Project/en.lproj/Localizable.strings")).toBe("en");
73
+ });
74
+ it("should handle regional codes like pt-BR.lproj", () => {
75
+ expect(extractLanguageFromLproj("/Project/pt-BR.lproj/Localizable.strings")).toBe("pt-BR");
76
+ expect(extractLanguageFromLproj("/Project/zh-Hans.lproj/Main.strings")).toBe("zh-Hans");
77
+ });
78
+ it("should return null for Base.lproj", () => {
79
+ expect(extractLanguageFromLproj("/Project/Base.lproj/Localizable.strings")).toBeNull();
80
+ });
81
+ it("should return null for non-lproj paths", () => {
82
+ expect(extractLanguageFromLproj("/Project/locales/en/file.json")).toBeNull();
83
+ expect(extractLanguageFromLproj("/Project/en/file.strings")).toBeNull();
84
+ });
85
+ it("should handle case-insensitive matching", () => {
86
+ expect(extractLanguageFromLproj("/Project/EN.lproj/file.strings")).toBe("EN");
87
+ });
88
+ });
89
+ describe("computeAppleLprojTargetPath", () => {
90
+ it("should compute correct target for .lproj structure", () => {
91
+ const result = computeAppleLprojTargetPath("/Project/en.lproj/Localizable.strings", "en", "de");
92
+ expect(result).toBe("/Project/de.lproj/Localizable.strings");
93
+ });
94
+ it("should handle nested paths", () => {
95
+ const result = computeAppleLprojTargetPath("/Project/Resources/en.lproj/Main.strings", "en", "fr");
96
+ expect(result).toBe("/Project/Resources/fr.lproj/Main.strings");
97
+ });
98
+ it("should handle regional codes", () => {
99
+ const result = computeAppleLprojTargetPath("/Project/pt-BR.lproj/Localizable.strings", "pt-BR", "es-MX");
100
+ expect(result).toBe("/Project/es-MX.lproj/Localizable.strings");
101
+ });
102
+ it("should return null for non-lproj paths", () => {
103
+ const result = computeAppleLprojTargetPath("/Project/locales/en/file.json", "en", "de");
104
+ expect(result).toBeNull();
105
+ });
106
+ });
107
+ });
108
+ //# sourceMappingURL=apple-common.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apple-common.test.js","sourceRoot":"","sources":["../../src/utils/apple-common.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAE3B,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,CAAC,aAAa,CAAC,uCAAuC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;QAC/B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,eAAe,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5D,MAAM,CAAC,eAAe,CAAC,gCAAgC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpD,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,CAAC,iBAAiB,CAAC,2CAA2C,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtD,MAAM,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,mBAAmB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,CAAC,mBAAmB,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACpD,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3D,MAAM,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,CAAC,uBAAuB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;QACxC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,CAAC,wBAAwB,CAAC,uCAAuC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,CAAC,wBAAwB,CAAC,0CAA0C,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3F,MAAM,CAAC,wBAAwB,CAAC,qCAAqC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,CAAC,wBAAwB,CAAC,yCAAyC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACzF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,wBAAwB,CAAC,+BAA+B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC7E,MAAM,CAAC,wBAAwB,CAAC,0BAA0B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,wBAAwB,CAAC,gCAAgC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;QAC3C,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,MAAM,GAAG,2BAA2B,CACxC,uCAAuC,EACvC,IAAI,EACJ,IAAI,CACL,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,MAAM,GAAG,2BAA2B,CACxC,0CAA0C,EAC1C,IAAI,EACJ,IAAI,CACL,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,MAAM,GAAG,2BAA2B,CACxC,0CAA0C,EAC1C,OAAO,EACP,OAAO,CACR,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,2BAA2B,CACxC,+BAA+B,EAC/B,IAAI,EACJ,IAAI,CACL,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Localizable.strings file parser
3
+ *
4
+ * Handles Apple's traditional localization format:
5
+ * - Key-value pairs: "key" = "value";
6
+ * - Comments: block and line comments
7
+ * - Escaped characters: quotes, newlines, tabs, backslashes, Unicode
8
+ */
9
+ import type { KeyValue } from "../api/types.js";
10
+ /**
11
+ * Parsed content from a .strings file
12
+ */
13
+ export interface StringsContent {
14
+ /** Key-value translation pairs */
15
+ entries: KeyValue[];
16
+ /** Comments associated with keys (key -> comment text) */
17
+ comments: Map<string, string>;
18
+ /** File-level header comment (at top of file) */
19
+ headerComment: string | null;
20
+ }
21
+ /**
22
+ * Parse a Localizable.strings file content
23
+ *
24
+ * Handles:
25
+ * - Basic key-value pairs: "key" = "value";
26
+ * - Block comments above entries for descriptions
27
+ * - Escaped quotes and special characters
28
+ * - Multi-line values
29
+ *
30
+ * @param content Raw file content
31
+ * @returns Parsed content with entries, comments, and header
32
+ */
33
+ export declare function parseStringsContent(content: string): StringsContent;
34
+ /**
35
+ * Escape a string for use in a .strings file
36
+ */
37
+ export declare function escapeStringsValue(value: string): string;
38
+ /**
39
+ * Reconstruct a .strings file from parsed content
40
+ *
41
+ * @param entries Translation key-value pairs
42
+ * @param comments Comments to associate with keys
43
+ * @param headerComment Optional file-level header comment
44
+ * @returns Formatted .strings file content
45
+ */
46
+ export declare function reconstructStringsContent(entries: KeyValue[], comments: Map<string, string>, headerComment: string | null): string;
47
+ /**
48
+ * Merge new translations into existing .strings content
49
+ *
50
+ * - Preserves existing translations not in newTranslations
51
+ * - Updates with new translations
52
+ * - Removes keys not in sourceKeys
53
+ * - Preserves comments from source
54
+ *
55
+ * @param existingContent Existing target file content (raw string)
56
+ * @param newTranslations New/updated translations from API
57
+ * @param sourceComments Comments from source file
58
+ * @param sourceKeys Set of all keys in source file
59
+ * @returns Merged .strings file content
60
+ */
61
+ export declare function mergeStringsContent(existingContent: string, newTranslations: KeyValue[], sourceComments: Map<string, string>, sourceKeys: Set<string>): string;
62
+ //# sourceMappingURL=strings-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings-parser.d.ts","sourceRoot":"","sources":["../../src/utils/strings-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,iDAAiD;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAmInE;AA2ED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,QAAQ,EAAE,EACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,GAC3B,MAAM,CAoBR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,QAAQ,EAAE,EAC3B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CA8BR"}
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Localizable.strings file parser
3
+ *
4
+ * Handles Apple's traditional localization format:
5
+ * - Key-value pairs: "key" = "value";
6
+ * - Comments: block and line comments
7
+ * - Escaped characters: quotes, newlines, tabs, backslashes, Unicode
8
+ */
9
+ /**
10
+ * Parse a Localizable.strings file content
11
+ *
12
+ * Handles:
13
+ * - Basic key-value pairs: "key" = "value";
14
+ * - Block comments above entries for descriptions
15
+ * - Escaped quotes and special characters
16
+ * - Multi-line values
17
+ *
18
+ * @param content Raw file content
19
+ * @returns Parsed content with entries, comments, and header
20
+ */
21
+ export function parseStringsContent(content) {
22
+ const entries = [];
23
+ const comments = new Map();
24
+ let headerComment = null;
25
+ // Track current position
26
+ let pos = 0;
27
+ let pendingComment = null;
28
+ let isFirstEntry = true;
29
+ while (pos < content.length) {
30
+ // Skip whitespace
31
+ while (pos < content.length && /\s/.test(content[pos])) {
32
+ pos++;
33
+ }
34
+ if (pos >= content.length)
35
+ break;
36
+ // Check for block comment /* ... */
37
+ if (content.slice(pos, pos + 2) === "/*") {
38
+ const commentEnd = content.indexOf("*/", pos + 2);
39
+ if (commentEnd === -1) {
40
+ // Unterminated comment, skip to end
41
+ break;
42
+ }
43
+ const commentText = content.slice(pos + 2, commentEnd).trim();
44
+ pendingComment = commentText;
45
+ pos = commentEnd + 2;
46
+ continue;
47
+ }
48
+ // Check for line comment // ...
49
+ if (content.slice(pos, pos + 2) === "//") {
50
+ const lineEnd = content.indexOf("\n", pos);
51
+ const commentText = lineEnd === -1
52
+ ? content.slice(pos + 2).trim()
53
+ : content.slice(pos + 2, lineEnd).trim();
54
+ pendingComment = commentText;
55
+ pos = lineEnd === -1 ? content.length : lineEnd + 1;
56
+ continue;
57
+ }
58
+ // Check for quoted key
59
+ if (content[pos] === '"') {
60
+ const keyResult = parseQuotedString(content, pos);
61
+ if (!keyResult) {
62
+ // Malformed, skip character
63
+ pos++;
64
+ continue;
65
+ }
66
+ const key = keyResult.value;
67
+ pos = keyResult.end;
68
+ // Skip whitespace
69
+ while (pos < content.length && /\s/.test(content[pos])) {
70
+ pos++;
71
+ }
72
+ // Expect '='
73
+ if (content[pos] !== "=") {
74
+ // Malformed, continue
75
+ continue;
76
+ }
77
+ pos++;
78
+ // Skip whitespace
79
+ while (pos < content.length && /\s/.test(content[pos])) {
80
+ pos++;
81
+ }
82
+ // Expect quoted value
83
+ if (content[pos] !== '"') {
84
+ // Malformed, continue
85
+ continue;
86
+ }
87
+ const valueResult = parseQuotedString(content, pos);
88
+ if (!valueResult) {
89
+ // Malformed, skip
90
+ pos++;
91
+ continue;
92
+ }
93
+ const value = valueResult.value;
94
+ pos = valueResult.end;
95
+ // Skip whitespace
96
+ while (pos < content.length && /\s/.test(content[pos])) {
97
+ pos++;
98
+ }
99
+ // Expect ';'
100
+ if (content[pos] === ";") {
101
+ pos++;
102
+ }
103
+ // Store entry
104
+ entries.push({ key, value });
105
+ // Handle comment
106
+ if (pendingComment !== null) {
107
+ if (isFirstEntry && entries.length === 1) {
108
+ // Check if this looks like a file header comment (contains copyright, license, etc.)
109
+ if (/copyright|license|generated|created by/i.test(pendingComment)) {
110
+ headerComment = pendingComment;
111
+ }
112
+ else {
113
+ comments.set(key, pendingComment);
114
+ }
115
+ }
116
+ else {
117
+ comments.set(key, pendingComment);
118
+ }
119
+ pendingComment = null;
120
+ }
121
+ isFirstEntry = false;
122
+ }
123
+ else {
124
+ // Skip unknown character
125
+ pos++;
126
+ }
127
+ }
128
+ // If there's a pending comment at the end with no entry, it might be a header
129
+ if (pendingComment !== null && entries.length === 0) {
130
+ headerComment = pendingComment;
131
+ }
132
+ return { entries, comments, headerComment };
133
+ }
134
+ /**
135
+ * Parse a quoted string starting at the given position
136
+ *
137
+ * @returns Object with unescaped value and end position, or null if malformed
138
+ */
139
+ function parseQuotedString(content, start) {
140
+ if (content[start] !== '"')
141
+ return null;
142
+ let pos = start + 1;
143
+ let value = "";
144
+ while (pos < content.length) {
145
+ const char = content[pos];
146
+ if (char === '"') {
147
+ // End of string
148
+ return { value, end: pos + 1 };
149
+ }
150
+ if (char === "\\") {
151
+ // Escape sequence
152
+ pos++;
153
+ if (pos >= content.length)
154
+ break;
155
+ const escaped = content[pos];
156
+ switch (escaped) {
157
+ case "n":
158
+ value += "\n";
159
+ break;
160
+ case "t":
161
+ value += "\t";
162
+ break;
163
+ case "r":
164
+ value += "\r";
165
+ break;
166
+ case '"':
167
+ value += '"';
168
+ break;
169
+ case "\\":
170
+ value += "\\";
171
+ break;
172
+ case "U":
173
+ case "u":
174
+ // Unicode escape: \U0000 or \u0000
175
+ const hexLength = escaped === "U" ? 4 : 4;
176
+ const hex = content.slice(pos + 1, pos + 1 + hexLength);
177
+ if (/^[0-9A-Fa-f]+$/.test(hex)) {
178
+ const codePoint = parseInt(hex, 16);
179
+ value += String.fromCodePoint(codePoint);
180
+ pos += hexLength;
181
+ }
182
+ else {
183
+ // Invalid unicode, keep as-is
184
+ value += "\\" + escaped;
185
+ }
186
+ break;
187
+ default:
188
+ // Unknown escape, keep both characters
189
+ value += "\\" + escaped;
190
+ }
191
+ pos++;
192
+ }
193
+ else {
194
+ value += char;
195
+ pos++;
196
+ }
197
+ }
198
+ // Unterminated string
199
+ return null;
200
+ }
201
+ /**
202
+ * Escape a string for use in a .strings file
203
+ */
204
+ export function escapeStringsValue(value) {
205
+ return value
206
+ .replace(/\\/g, "\\\\")
207
+ .replace(/"/g, '\\"')
208
+ .replace(/\n/g, "\\n")
209
+ .replace(/\r/g, "\\r")
210
+ .replace(/\t/g, "\\t");
211
+ }
212
+ /**
213
+ * Reconstruct a .strings file from parsed content
214
+ *
215
+ * @param entries Translation key-value pairs
216
+ * @param comments Comments to associate with keys
217
+ * @param headerComment Optional file-level header comment
218
+ * @returns Formatted .strings file content
219
+ */
220
+ export function reconstructStringsContent(entries, comments, headerComment) {
221
+ const lines = [];
222
+ // Add header comment if present
223
+ if (headerComment) {
224
+ lines.push(`/* ${headerComment} */`);
225
+ lines.push("");
226
+ }
227
+ // Add each entry with its comment
228
+ for (const { key, value } of entries) {
229
+ const comment = comments.get(key);
230
+ if (comment) {
231
+ lines.push(`/* ${comment} */`);
232
+ }
233
+ lines.push(`"${escapeStringsValue(key)}" = "${escapeStringsValue(value)}";`);
234
+ }
235
+ // Ensure trailing newline
236
+ return lines.join("\n") + "\n";
237
+ }
238
+ /**
239
+ * Merge new translations into existing .strings content
240
+ *
241
+ * - Preserves existing translations not in newTranslations
242
+ * - Updates with new translations
243
+ * - Removes keys not in sourceKeys
244
+ * - Preserves comments from source
245
+ *
246
+ * @param existingContent Existing target file content (raw string)
247
+ * @param newTranslations New/updated translations from API
248
+ * @param sourceComments Comments from source file
249
+ * @param sourceKeys Set of all keys in source file
250
+ * @returns Merged .strings file content
251
+ */
252
+ export function mergeStringsContent(existingContent, newTranslations, sourceComments, sourceKeys) {
253
+ // Parse existing content
254
+ const existing = parseStringsContent(existingContent);
255
+ const existingMap = new Map();
256
+ for (const { key, value } of existing.entries) {
257
+ existingMap.set(key, value);
258
+ }
259
+ // Create map of new translations
260
+ const newMap = new Map();
261
+ for (const { key, value } of newTranslations) {
262
+ newMap.set(key, value);
263
+ }
264
+ // Build merged entries - only include keys from source
265
+ const mergedEntries = [];
266
+ for (const key of sourceKeys) {
267
+ // Prefer new translation, then existing, skip if neither
268
+ const value = newMap.get(key) ?? existingMap.get(key);
269
+ if (value !== undefined) {
270
+ mergedEntries.push({ key, value });
271
+ }
272
+ }
273
+ // Use source comments, preserve existing header
274
+ return reconstructStringsContent(mergedEntries, sourceComments, existing.headerComment);
275
+ }
276
+ //# sourceMappingURL=strings-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings-parser.js","sourceRoot":"","sources":["../../src/utils/strings-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=strings-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings-parser.test.d.ts","sourceRoot":"","sources":["../../src/utils/strings-parser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseStringsContent, reconstructStringsContent, mergeStringsContent, escapeStringsValue, } from "./strings-parser.js";
3
+ describe("Strings Parser", () => {
4
+ describe("parseStringsContent", () => {
5
+ it("should parse simple key-value pairs", () => {
6
+ const content = `"greeting" = "Hello";
7
+ "farewell" = "Goodbye";`;
8
+ const result = parseStringsContent(content);
9
+ expect(result.entries).toEqual([
10
+ { key: "greeting", value: "Hello" },
11
+ { key: "farewell", value: "Goodbye" },
12
+ ]);
13
+ });
14
+ it("should handle escaped quotes", () => {
15
+ const content = `"message" = "He said \\"Hello\\"";`;
16
+ const result = parseStringsContent(content);
17
+ expect(result.entries).toEqual([
18
+ { key: "message", value: 'He said "Hello"' },
19
+ ]);
20
+ });
21
+ it("should handle escaped special characters", () => {
22
+ const content = `"message" = "Line1\\nLine2\\tTabbed";`;
23
+ const result = parseStringsContent(content);
24
+ expect(result.entries).toEqual([
25
+ { key: "message", value: "Line1\nLine2\tTabbed" },
26
+ ]);
27
+ });
28
+ it("should preserve comments above entries", () => {
29
+ const content = `/* User greeting */
30
+ "greeting" = "Hello";
31
+ /* User farewell */
32
+ "farewell" = "Goodbye";`;
33
+ const result = parseStringsContent(content);
34
+ expect(result.entries).toHaveLength(2);
35
+ expect(result.comments.get("greeting")).toBe("User greeting");
36
+ expect(result.comments.get("farewell")).toBe("User farewell");
37
+ });
38
+ it("should handle empty file", () => {
39
+ const result = parseStringsContent("");
40
+ expect(result.entries).toEqual([]);
41
+ expect(result.comments.size).toBe(0);
42
+ });
43
+ it("should handle file-level header comment", () => {
44
+ const content = `/* Copyright 2024 Company */
45
+ "greeting" = "Hello";`;
46
+ const result = parseStringsContent(content);
47
+ expect(result.headerComment).toBe("Copyright 2024 Company");
48
+ expect(result.entries).toHaveLength(1);
49
+ });
50
+ it("should handle line comments", () => {
51
+ const content = `// This is a greeting
52
+ "greeting" = "Hello";`;
53
+ const result = parseStringsContent(content);
54
+ expect(result.comments.get("greeting")).toBe("This is a greeting");
55
+ });
56
+ it("should parse Unicode escape sequences", () => {
57
+ const content = `"char" = "caf\\U00E9";`;
58
+ const result = parseStringsContent(content);
59
+ expect(result.entries).toEqual([
60
+ { key: "char", value: "café" },
61
+ ]);
62
+ });
63
+ });
64
+ describe("escapeStringsValue", () => {
65
+ it("should escape quotes", () => {
66
+ expect(escapeStringsValue('Say "Hello"')).toBe('Say \\"Hello\\"');
67
+ });
68
+ it("should escape newlines and tabs", () => {
69
+ expect(escapeStringsValue("Line1\nLine2")).toBe("Line1\\nLine2");
70
+ expect(escapeStringsValue("Col1\tCol2")).toBe("Col1\\tCol2");
71
+ });
72
+ it("should escape backslashes", () => {
73
+ expect(escapeStringsValue("path\\to\\file")).toBe("path\\\\to\\\\file");
74
+ });
75
+ });
76
+ describe("reconstructStringsContent", () => {
77
+ it("should rebuild with proper formatting", () => {
78
+ const entries = [
79
+ { key: "greeting", value: "Hello" },
80
+ { key: "farewell", value: "Goodbye" },
81
+ ];
82
+ const comments = new Map();
83
+ const result = reconstructStringsContent(entries, comments, null);
84
+ expect(result).toBe('"greeting" = "Hello";\n"farewell" = "Goodbye";\n');
85
+ });
86
+ it("should preserve comments", () => {
87
+ const entries = [{ key: "greeting", value: "Hello" }];
88
+ const comments = new Map([["greeting", "User greeting"]]);
89
+ const result = reconstructStringsContent(entries, comments, null);
90
+ expect(result).toBe('/* User greeting */\n"greeting" = "Hello";\n');
91
+ });
92
+ it("should add header comment", () => {
93
+ const entries = [{ key: "key", value: "value" }];
94
+ const comments = new Map();
95
+ const result = reconstructStringsContent(entries, comments, "Header");
96
+ expect(result).toContain("/* Header */");
97
+ });
98
+ it("should escape special characters in output", () => {
99
+ const entries = [{ key: "msg", value: 'Say "Hi"\nOK' }];
100
+ const comments = new Map();
101
+ const result = reconstructStringsContent(entries, comments, null);
102
+ expect(result).toBe('"msg" = "Say \\"Hi\\"\\nOK";\n');
103
+ });
104
+ });
105
+ describe("mergeStringsContent", () => {
106
+ it("should merge new translations", () => {
107
+ const existing = '"greeting" = "Hallo";';
108
+ const newTranslations = [{ key: "farewell", value: "Auf Wiedersehen" }];
109
+ const sourceComments = new Map();
110
+ const sourceKeys = new Set(["greeting", "farewell"]);
111
+ const result = mergeStringsContent(existing, newTranslations, sourceComments, sourceKeys);
112
+ expect(result).toContain('"greeting" = "Hallo"');
113
+ expect(result).toContain('"farewell" = "Auf Wiedersehen"');
114
+ });
115
+ it("should preserve existing translations not updated", () => {
116
+ const existing = '"greeting" = "Hallo";\n"farewell" = "Tschüss";';
117
+ const newTranslations = [{ key: "greeting", value: "Guten Tag" }];
118
+ const sourceComments = new Map();
119
+ const sourceKeys = new Set(["greeting", "farewell"]);
120
+ const result = mergeStringsContent(existing, newTranslations, sourceComments, sourceKeys);
121
+ expect(result).toContain('"greeting" = "Guten Tag"');
122
+ expect(result).toContain('"farewell" = "Tschüss"');
123
+ });
124
+ it("should remove keys not in source", () => {
125
+ const existing = '"greeting" = "Hallo";\n"deleted" = "To be removed";';
126
+ const newTranslations = [];
127
+ const sourceComments = new Map();
128
+ const sourceKeys = new Set(["greeting"]);
129
+ const result = mergeStringsContent(existing, newTranslations, sourceComments, sourceKeys);
130
+ expect(result).toContain('"greeting" = "Hallo"');
131
+ expect(result).not.toContain("deleted");
132
+ });
133
+ it("should preserve comment associations from source", () => {
134
+ const existing = '"greeting" = "Hallo";';
135
+ const newTranslations = [];
136
+ const sourceComments = new Map([["greeting", "User greeting"]]);
137
+ const sourceKeys = new Set(["greeting"]);
138
+ const result = mergeStringsContent(existing, newTranslations, sourceComments, sourceKeys);
139
+ expect(result).toContain("/* User greeting */");
140
+ });
141
+ });
142
+ });
143
+ //# sourceMappingURL=strings-parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings-parser.test.js","sourceRoot":"","sources":["../../src/utils/strings-parser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAE7B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,OAAO,GAAG;wBACE,CAAC;YAEnB,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;gBAC7B,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE;gBACnC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE;aACtC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,OAAO,GAAG,oCAAoC,CAAC;YAErD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;gBAC7B,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE;aAC7C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,OAAO,GAAG,uCAAuC,CAAC;YAExD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;gBAC7B,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,sBAAsB,EAAE;aAClD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,OAAO,GAAG;;;wBAGE,CAAC;YAEnB,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC9D,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,MAAM,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;YAEvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,OAAO,GAAG;sBACA,CAAC;YAEjB,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YAC5D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,OAAO,GAAG;sBACA,CAAC;YAEjB,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACrE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAG,wBAAwB,CAAC;YAEzC,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;gBAC7B,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;aAC/B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;YAC9B,MAAM,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACjE,MAAM,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAG;gBACd,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE;gBACnC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE;aACtC,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAE3C,MAAM,MAAM,GAAG,yBAAyB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YAElE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,OAAO,GAAG,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;YAE1D,MAAM,MAAM,GAAG,yBAAyB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YAElE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,OAAO,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAE3C,MAAM,MAAM,GAAG,yBAAyB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAEtE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,OAAO,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAE3C,MAAM,MAAM,GAAG,yBAAyB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YAElE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,QAAQ,GAAG,uBAAuB,CAAC;YACzC,MAAM,eAAe,GAAG,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACxE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;YACjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;YAErD,MAAM,MAAM,GAAG,mBAAmB,CAChC,QAAQ,EACR,eAAe,EACf,cAAc,EACd,UAAU,CACX,CAAC;YAEF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC3D,MAAM,QAAQ,GAAG,gDAAgD,CAAC;YAClE,MAAM,eAAe,GAAG,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;YAClE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;YACjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;YAErD,MAAM,MAAM,GAAG,mBAAmB,CAChC,QAAQ,EACR,eAAe,EACf,cAAc,EACd,UAAU,CACX,CAAC;YAEF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;YACrD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,QAAQ,GAAG,qDAAqD,CAAC;YACvE,MAAM,eAAe,GAA0C,EAAE,CAAC;YAClE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;YACjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;YAEzC,MAAM,MAAM,GAAG,mBAAmB,CAChC,QAAQ,EACR,eAAe,EACf,cAAc,EACd,UAAU,CACX,CAAC;YAEF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,QAAQ,GAAG,uBAAuB,CAAC;YACzC,MAAM,eAAe,GAA0C,EAAE,CAAC;YAClE,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;YAChE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;YAEzC,MAAM,MAAM,GAAG,mBAAmB,CAChC,QAAQ,EACR,eAAe,EACf,cAAc,EACd,UAAU,CACX,CAAC;YAEF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}