@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.
- package/dist/handlers/index.d.ts +7 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +7 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/xcstrings-sync-handler.d.ts +72 -0
- package/dist/handlers/xcstrings-sync-handler.d.ts.map +1 -0
- package/dist/handlers/xcstrings-sync-handler.js +126 -0
- package/dist/handlers/xcstrings-sync-handler.js.map +1 -0
- package/dist/locale-detection/index.d.ts.map +1 -1
- package/dist/locale-detection/index.js +68 -4
- package/dist/locale-detection/index.js.map +1 -1
- package/dist/locale-detection/patterns.d.ts.map +1 -1
- package/dist/locale-detection/patterns.js +30 -0
- package/dist/locale-detection/patterns.js.map +1 -1
- package/dist/tools/get-translation-status.d.ts.map +1 -1
- package/dist/tools/get-translation-status.js +34 -6
- package/dist/tools/get-translation-status.js.map +1 -1
- package/dist/tools/list-local-locales.d.ts +1 -1
- package/dist/tools/list-local-locales.js +2 -2
- package/dist/tools/list-local-locales.js.map +1 -1
- package/dist/tools/sync-translations.d.ts.map +1 -1
- package/dist/tools/sync-translations.js +199 -40
- package/dist/tools/sync-translations.js.map +1 -1
- package/dist/utils/apple-common.d.ts +63 -0
- package/dist/utils/apple-common.d.ts.map +1 -0
- package/dist/utils/apple-common.js +118 -0
- package/dist/utils/apple-common.js.map +1 -0
- package/dist/utils/apple-common.test.d.ts +2 -0
- package/dist/utils/apple-common.test.d.ts.map +1 -0
- package/dist/utils/apple-common.test.js +108 -0
- package/dist/utils/apple-common.test.js.map +1 -0
- package/dist/utils/strings-parser.d.ts +62 -0
- package/dist/utils/strings-parser.d.ts.map +1 -0
- package/dist/utils/strings-parser.js +276 -0
- package/dist/utils/strings-parser.js.map +1 -0
- package/dist/utils/strings-parser.test.d.ts +2 -0
- package/dist/utils/strings-parser.test.d.ts.map +1 -0
- package/dist/utils/strings-parser.test.js +143 -0
- package/dist/utils/strings-parser.test.js.map +1 -0
- package/dist/utils/stringsdict-parser.d.ts +106 -0
- package/dist/utils/stringsdict-parser.d.ts.map +1 -0
- package/dist/utils/stringsdict-parser.js +473 -0
- package/dist/utils/stringsdict-parser.js.map +1 -0
- package/dist/utils/stringsdict-parser.test.d.ts +2 -0
- package/dist/utils/stringsdict-parser.test.d.ts.map +1 -0
- package/dist/utils/stringsdict-parser.test.js +244 -0
- package/dist/utils/stringsdict-parser.test.js.map +1 -0
- package/dist/utils/xcstrings-parser.d.ts +128 -0
- package/dist/utils/xcstrings-parser.d.ts.map +1 -0
- package/dist/utils/xcstrings-parser.js +216 -0
- package/dist/utils/xcstrings-parser.js.map +1 -0
- package/dist/utils/xcstrings-parser.test.d.ts +2 -0
- package/dist/utils/xcstrings-parser.test.d.ts.map +1 -0
- package/dist/utils/xcstrings-parser.test.js +157 -0
- package/dist/utils/xcstrings-parser.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseStringsDictContent, flattenStringsDictForApi, unflattenStringsDictFromApi, reconstructStringsDictContent, mergeStringsDictContent, } from "./stringsdict-parser.js";
|
|
3
|
+
describe("Stringsdict Parser", () => {
|
|
4
|
+
const sampleXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
5
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
6
|
+
<plist version="1.0">
|
|
7
|
+
<dict>
|
|
8
|
+
<key>items_count</key>
|
|
9
|
+
<dict>
|
|
10
|
+
<key>NSStringLocalizedFormatKey</key>
|
|
11
|
+
<string>%#@count@</string>
|
|
12
|
+
<key>count</key>
|
|
13
|
+
<dict>
|
|
14
|
+
<key>NSStringFormatSpecTypeKey</key>
|
|
15
|
+
<string>NSStringPluralRuleType</string>
|
|
16
|
+
<key>one</key>
|
|
17
|
+
<string>%d item</string>
|
|
18
|
+
<key>other</key>
|
|
19
|
+
<string>%d items</string>
|
|
20
|
+
</dict>
|
|
21
|
+
</dict>
|
|
22
|
+
</dict>
|
|
23
|
+
</plist>`;
|
|
24
|
+
describe("parseStringsDictContent", () => {
|
|
25
|
+
it("should parse plural rules", () => {
|
|
26
|
+
const result = parseStringsDictContent(sampleXml);
|
|
27
|
+
expect(result).not.toBeNull();
|
|
28
|
+
expect(result.entries).toHaveLength(1);
|
|
29
|
+
expect(result.entries[0].key).toBe("items_count");
|
|
30
|
+
});
|
|
31
|
+
it("should extract NSStringLocalizedFormatKey", () => {
|
|
32
|
+
const result = parseStringsDictContent(sampleXml);
|
|
33
|
+
expect(result.entries[0].formatKey).toBe("%#@count@");
|
|
34
|
+
});
|
|
35
|
+
it("should handle multiple plural variants", () => {
|
|
36
|
+
const result = parseStringsDictContent(sampleXml);
|
|
37
|
+
const entry = result.entries[0];
|
|
38
|
+
const rule = entry.pluralRules["count"];
|
|
39
|
+
expect(rule.variants.one).toBe("%d item");
|
|
40
|
+
expect(rule.variants.other).toBe("%d items");
|
|
41
|
+
});
|
|
42
|
+
it("should parse nested dict structure", () => {
|
|
43
|
+
const result = parseStringsDictContent(sampleXml);
|
|
44
|
+
const entry = result.entries[0];
|
|
45
|
+
expect(entry.pluralRules["count"]).toBeDefined();
|
|
46
|
+
expect(entry.pluralRules["count"].specTypeKey).toBe("NSStringPluralRuleType");
|
|
47
|
+
});
|
|
48
|
+
it("should return null for invalid XML", () => {
|
|
49
|
+
const result = parseStringsDictContent("invalid xml");
|
|
50
|
+
expect(result).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
it("should handle empty plist", () => {
|
|
53
|
+
const emptyXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
54
|
+
<plist version="1.0">
|
|
55
|
+
<dict>
|
|
56
|
+
</dict>
|
|
57
|
+
</plist>`;
|
|
58
|
+
const result = parseStringsDictContent(emptyXml);
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
expect(result.entries).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("flattenStringsDictForApi", () => {
|
|
64
|
+
it("should flatten plural entries with dot notation", () => {
|
|
65
|
+
const entries = [
|
|
66
|
+
{
|
|
67
|
+
key: "items_count",
|
|
68
|
+
formatKey: "%#@count@",
|
|
69
|
+
pluralRules: {
|
|
70
|
+
count: {
|
|
71
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
72
|
+
variants: {
|
|
73
|
+
one: "%d item",
|
|
74
|
+
other: "%d items",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
const result = flattenStringsDictForApi(entries);
|
|
81
|
+
expect(result).toContainEqual({
|
|
82
|
+
key: "items_count.count.one",
|
|
83
|
+
value: "%d item",
|
|
84
|
+
});
|
|
85
|
+
expect(result).toContainEqual({
|
|
86
|
+
key: "items_count.count.other",
|
|
87
|
+
value: "%d items",
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it("should include format key", () => {
|
|
91
|
+
const entries = [
|
|
92
|
+
{
|
|
93
|
+
key: "test",
|
|
94
|
+
formatKey: "%#@var@",
|
|
95
|
+
pluralRules: {},
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
const result = flattenStringsDictForApi(entries);
|
|
99
|
+
expect(result).toContainEqual({
|
|
100
|
+
key: "test.__formatKey",
|
|
101
|
+
value: "%#@var@",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("unflattenStringsDictFromApi", () => {
|
|
106
|
+
it("should reconstruct plural structure", () => {
|
|
107
|
+
const translations = [
|
|
108
|
+
{ key: "items_count.count.one", value: "%d Artikel" },
|
|
109
|
+
{ key: "items_count.count.other", value: "%d Artikel" },
|
|
110
|
+
];
|
|
111
|
+
const sourceEntries = [
|
|
112
|
+
{
|
|
113
|
+
key: "items_count",
|
|
114
|
+
formatKey: "%#@count@",
|
|
115
|
+
pluralRules: {
|
|
116
|
+
count: {
|
|
117
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
118
|
+
variants: { one: "%d item", other: "%d items" },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
const result = unflattenStringsDictFromApi(translations, sourceEntries);
|
|
124
|
+
expect(result[0].pluralRules.count.variants.one).toBe("%d Artikel");
|
|
125
|
+
expect(result[0].pluralRules.count.variants.other).toBe("%d Artikel");
|
|
126
|
+
});
|
|
127
|
+
it("should handle missing variants", () => {
|
|
128
|
+
const translations = [
|
|
129
|
+
{ key: "items_count.count.one", value: "%d Artikel" },
|
|
130
|
+
// 'other' is missing
|
|
131
|
+
];
|
|
132
|
+
const sourceEntries = [
|
|
133
|
+
{
|
|
134
|
+
key: "items_count",
|
|
135
|
+
formatKey: "%#@count@",
|
|
136
|
+
pluralRules: {
|
|
137
|
+
count: {
|
|
138
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
139
|
+
variants: { one: "%d item", other: "%d items" },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
const result = unflattenStringsDictFromApi(translations, sourceEntries);
|
|
145
|
+
expect(result[0].pluralRules.count.variants.one).toBe("%d Artikel");
|
|
146
|
+
expect(result[0].pluralRules.count.variants.other).toBe("%d items"); // Kept from source
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe("reconstructStringsDictContent", () => {
|
|
150
|
+
it("should generate valid XML plist", () => {
|
|
151
|
+
const entries = [
|
|
152
|
+
{
|
|
153
|
+
key: "items_count",
|
|
154
|
+
formatKey: "%#@count@",
|
|
155
|
+
pluralRules: {
|
|
156
|
+
count: {
|
|
157
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
158
|
+
variants: { one: "%d item", other: "%d items" },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
const result = reconstructStringsDictContent(entries);
|
|
164
|
+
expect(result).toContain('<?xml version="1.0"');
|
|
165
|
+
expect(result).toContain("<plist version=\"1.0\">");
|
|
166
|
+
expect(result).toContain("<key>items_count</key>");
|
|
167
|
+
expect(result).toContain("<key>NSStringLocalizedFormatKey</key>");
|
|
168
|
+
expect(result).toContain("<string>%#@count@</string>");
|
|
169
|
+
});
|
|
170
|
+
it("should maintain proper element order", () => {
|
|
171
|
+
const entries = [
|
|
172
|
+
{
|
|
173
|
+
key: "test",
|
|
174
|
+
formatKey: "%#@var@",
|
|
175
|
+
pluralRules: {
|
|
176
|
+
var: {
|
|
177
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
178
|
+
variants: { zero: "none", one: "one", other: "many" },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
const result = reconstructStringsDictContent(entries);
|
|
184
|
+
// Variants should be in consistent order
|
|
185
|
+
const zeroIndex = result.indexOf("<key>zero</key>");
|
|
186
|
+
const oneIndex = result.indexOf("<key>one</key>");
|
|
187
|
+
const otherIndex = result.indexOf("<key>other</key>");
|
|
188
|
+
expect(zeroIndex).toBeLessThan(oneIndex);
|
|
189
|
+
expect(oneIndex).toBeLessThan(otherIndex);
|
|
190
|
+
});
|
|
191
|
+
it("should have trailing newline", () => {
|
|
192
|
+
const entries = [];
|
|
193
|
+
const result = reconstructStringsDictContent(entries);
|
|
194
|
+
expect(result.endsWith("\n")).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe("mergeStringsDictContent", () => {
|
|
198
|
+
it("should merge new translations", () => {
|
|
199
|
+
const existing = sampleXml;
|
|
200
|
+
const newTranslations = [
|
|
201
|
+
{ key: "items_count.count.one", value: "%d Artikel" },
|
|
202
|
+
{ key: "items_count.count.other", value: "%d Artikel" },
|
|
203
|
+
];
|
|
204
|
+
const sourceEntries = [
|
|
205
|
+
{
|
|
206
|
+
key: "items_count",
|
|
207
|
+
formatKey: "%#@count@",
|
|
208
|
+
pluralRules: {
|
|
209
|
+
count: {
|
|
210
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
211
|
+
variants: { one: "%d item", other: "%d items" },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
const sourceKeys = new Set(["items_count"]);
|
|
217
|
+
const result = mergeStringsDictContent(existing, newTranslations, sourceEntries, sourceKeys);
|
|
218
|
+
expect(result).toContain("%d Artikel");
|
|
219
|
+
});
|
|
220
|
+
it("should handle empty existing content", () => {
|
|
221
|
+
const newTranslations = [
|
|
222
|
+
{ key: "items_count.count.one", value: "%d élément" },
|
|
223
|
+
{ key: "items_count.count.other", value: "%d éléments" },
|
|
224
|
+
];
|
|
225
|
+
const sourceEntries = [
|
|
226
|
+
{
|
|
227
|
+
key: "items_count",
|
|
228
|
+
formatKey: "%#@count@",
|
|
229
|
+
pluralRules: {
|
|
230
|
+
count: {
|
|
231
|
+
specTypeKey: "NSStringPluralRuleType",
|
|
232
|
+
variants: { one: "%d item", other: "%d items" },
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
const sourceKeys = new Set(["items_count"]);
|
|
238
|
+
const result = mergeStringsDictContent("", newTranslations, sourceEntries, sourceKeys);
|
|
239
|
+
expect(result).toContain("%d élément");
|
|
240
|
+
expect(result).toContain("%d éléments");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
//# sourceMappingURL=stringsdict-parser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringsdict-parser.test.js","sourceRoot":"","sources":["../../src/utils/stringsdict-parser.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Catalog (.xcstrings) file parser
|
|
3
|
+
*
|
|
4
|
+
* String Catalogs are JSON-based localization files introduced in Xcode 15.
|
|
5
|
+
* Key characteristics:
|
|
6
|
+
* - Single file contains ALL languages
|
|
7
|
+
* - JSON structure with sourceLanguage, version, and strings
|
|
8
|
+
* - Each string entry can have localizations for multiple languages
|
|
9
|
+
* - Supports metadata like extractionState and comments
|
|
10
|
+
*/
|
|
11
|
+
import type { KeyValue } from "../api/types.js";
|
|
12
|
+
/**
|
|
13
|
+
* String unit state - translation status
|
|
14
|
+
*/
|
|
15
|
+
export type StringUnitState = "translated" | "needs_review" | "new" | "stale";
|
|
16
|
+
/**
|
|
17
|
+
* String unit - the actual translated value
|
|
18
|
+
*/
|
|
19
|
+
export interface StringUnit {
|
|
20
|
+
state: StringUnitState;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Localization entry for a specific language
|
|
25
|
+
*/
|
|
26
|
+
export interface XCLocalization {
|
|
27
|
+
stringUnit?: StringUnit;
|
|
28
|
+
/** Variations for plurals, device-specific strings, etc. */
|
|
29
|
+
variations?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Entry for a single string key
|
|
33
|
+
*/
|
|
34
|
+
export interface XCStringEntry {
|
|
35
|
+
/** Manual, migrated, stale, etc. */
|
|
36
|
+
extractionState?: string;
|
|
37
|
+
/** Developer comment/description */
|
|
38
|
+
comment?: string;
|
|
39
|
+
/** Localizations keyed by language code */
|
|
40
|
+
localizations?: Record<string, XCLocalization>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Root structure of an .xcstrings file
|
|
44
|
+
*/
|
|
45
|
+
export interface XCStringsFile {
|
|
46
|
+
sourceLanguage: string;
|
|
47
|
+
version: string;
|
|
48
|
+
strings: Record<string, XCStringEntry>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parsed content from an .xcstrings file
|
|
52
|
+
*/
|
|
53
|
+
export interface XCStringsContent {
|
|
54
|
+
/** Source language code */
|
|
55
|
+
sourceLanguage: string;
|
|
56
|
+
/** File format version */
|
|
57
|
+
version: string;
|
|
58
|
+
/** Translation entries for source language */
|
|
59
|
+
entries: KeyValue[];
|
|
60
|
+
/** All localizations by language */
|
|
61
|
+
allLocalizations: Map<string, KeyValue[]>;
|
|
62
|
+
/** Full parsed structure for preservation */
|
|
63
|
+
metadata: XCStringsFile;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse an .xcstrings file content
|
|
67
|
+
*
|
|
68
|
+
* @param content Raw JSON content
|
|
69
|
+
* @returns Parsed content or null if invalid
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseXCStringsContent(content: string): XCStringsContent | null;
|
|
72
|
+
/**
|
|
73
|
+
* Extract translations for a specific locale from an .xcstrings file
|
|
74
|
+
*
|
|
75
|
+
* @param xcstrings Parsed xcstrings data
|
|
76
|
+
* @param locale Language code to extract
|
|
77
|
+
* @returns Array of key-value pairs for the locale
|
|
78
|
+
*/
|
|
79
|
+
export declare function extractLocaleFromXCStrings(xcstrings: XCStringsFile, locale: string): KeyValue[];
|
|
80
|
+
/**
|
|
81
|
+
* Get all language codes present in an .xcstrings file
|
|
82
|
+
*
|
|
83
|
+
* @param xcstrings Parsed xcstrings data
|
|
84
|
+
* @returns Set of language codes
|
|
85
|
+
*/
|
|
86
|
+
export declare function getXCStringsLanguages(xcstrings: XCStringsFile): Set<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Update a specific locale's translations in an .xcstrings file
|
|
89
|
+
*
|
|
90
|
+
* This adds or updates translations for a single language without
|
|
91
|
+
* affecting other languages in the file.
|
|
92
|
+
*
|
|
93
|
+
* @param xcstrings Original xcstrings data
|
|
94
|
+
* @param locale Target language code
|
|
95
|
+
* @param translations New translations for the locale
|
|
96
|
+
* @returns Updated xcstrings data
|
|
97
|
+
*/
|
|
98
|
+
export declare function updateXCStringsLocale(xcstrings: XCStringsFile, locale: string, translations: KeyValue[]): XCStringsFile;
|
|
99
|
+
/**
|
|
100
|
+
* Merge new translations into an .xcstrings file for a specific locale
|
|
101
|
+
*
|
|
102
|
+
* - Preserves existing translations for the locale that aren't updated
|
|
103
|
+
* - Preserves all other languages completely
|
|
104
|
+
* - Removes keys that no longer exist in source
|
|
105
|
+
* - Updates state to "translated" for new translations
|
|
106
|
+
*
|
|
107
|
+
* @param existing Existing xcstrings data
|
|
108
|
+
* @param locale Target language code
|
|
109
|
+
* @param newTranslations New/updated translations
|
|
110
|
+
* @param sourceKeys Set of all keys in source language
|
|
111
|
+
* @returns Merged xcstrings data
|
|
112
|
+
*/
|
|
113
|
+
export declare function mergeXCStringsContent(existing: XCStringsFile, locale: string, newTranslations: KeyValue[], sourceKeys: Set<string>): XCStringsFile;
|
|
114
|
+
/**
|
|
115
|
+
* Reconstruct .xcstrings JSON content with proper formatting
|
|
116
|
+
*
|
|
117
|
+
* @param xcstrings XCStrings data to serialize
|
|
118
|
+
* @returns Formatted JSON string
|
|
119
|
+
*/
|
|
120
|
+
export declare function reconstructXCStringsContent(xcstrings: XCStringsFile): string;
|
|
121
|
+
/**
|
|
122
|
+
* Create a new empty .xcstrings structure
|
|
123
|
+
*
|
|
124
|
+
* @param sourceLanguage Source language code
|
|
125
|
+
* @returns New empty xcstrings structure
|
|
126
|
+
*/
|
|
127
|
+
export declare function createEmptyXCStrings(sourceLanguage: string): XCStringsFile;
|
|
128
|
+
//# sourceMappingURL=xcstrings-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcstrings-parser.d.ts","sourceRoot":"","sources":["../../src/utils/xcstrings-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,cAAc,GAAG,KAAK,GAAG,OAAO,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2BAA2B;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,oCAAoC;IACpC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1C,6CAA6C;IAC7C,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA2D9E;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,MAAM,GACb,QAAQ,EAAE,CAWZ;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAY3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,QAAQ,EAAE,GACvB,aAAa,CA8Bf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,QAAQ,EAAE,EAC3B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,aAAa,CA2Cf;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,CAG5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,CAM1E"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Catalog (.xcstrings) file parser
|
|
3
|
+
*
|
|
4
|
+
* String Catalogs are JSON-based localization files introduced in Xcode 15.
|
|
5
|
+
* Key characteristics:
|
|
6
|
+
* - Single file contains ALL languages
|
|
7
|
+
* - JSON structure with sourceLanguage, version, and strings
|
|
8
|
+
* - Each string entry can have localizations for multiple languages
|
|
9
|
+
* - Supports metadata like extractionState and comments
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Parse an .xcstrings file content
|
|
13
|
+
*
|
|
14
|
+
* @param content Raw JSON content
|
|
15
|
+
* @returns Parsed content or null if invalid
|
|
16
|
+
*/
|
|
17
|
+
export function parseXCStringsContent(content) {
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(content);
|
|
20
|
+
if (!data.sourceLanguage || !data.strings) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const sourceLanguage = data.sourceLanguage;
|
|
24
|
+
const version = data.version || "1.0";
|
|
25
|
+
const entries = [];
|
|
26
|
+
const allLocalizations = new Map();
|
|
27
|
+
// Extract all languages and their translations
|
|
28
|
+
const languages = new Set();
|
|
29
|
+
for (const [key, entry] of Object.entries(data.strings)) {
|
|
30
|
+
if (entry.localizations) {
|
|
31
|
+
for (const lang of Object.keys(entry.localizations)) {
|
|
32
|
+
languages.add(lang);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Initialize language maps
|
|
37
|
+
for (const lang of languages) {
|
|
38
|
+
allLocalizations.set(lang, []);
|
|
39
|
+
}
|
|
40
|
+
// Extract translations for each key
|
|
41
|
+
for (const [key, entry] of Object.entries(data.strings)) {
|
|
42
|
+
const localizations = entry.localizations || {};
|
|
43
|
+
// Get source language translation for entries
|
|
44
|
+
const sourceLocalization = localizations[sourceLanguage];
|
|
45
|
+
if (sourceLocalization?.stringUnit?.value) {
|
|
46
|
+
entries.push({ key, value: sourceLocalization.stringUnit.value });
|
|
47
|
+
}
|
|
48
|
+
// Extract all localizations
|
|
49
|
+
for (const [lang, localization] of Object.entries(localizations)) {
|
|
50
|
+
if (localization.stringUnit?.value) {
|
|
51
|
+
const langEntries = allLocalizations.get(lang) || [];
|
|
52
|
+
langEntries.push({ key, value: localization.stringUnit.value });
|
|
53
|
+
allLocalizations.set(lang, langEntries);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
sourceLanguage,
|
|
59
|
+
version,
|
|
60
|
+
entries,
|
|
61
|
+
allLocalizations,
|
|
62
|
+
metadata: data,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Extract translations for a specific locale from an .xcstrings file
|
|
71
|
+
*
|
|
72
|
+
* @param xcstrings Parsed xcstrings data
|
|
73
|
+
* @param locale Language code to extract
|
|
74
|
+
* @returns Array of key-value pairs for the locale
|
|
75
|
+
*/
|
|
76
|
+
export function extractLocaleFromXCStrings(xcstrings, locale) {
|
|
77
|
+
const entries = [];
|
|
78
|
+
for (const [key, entry] of Object.entries(xcstrings.strings)) {
|
|
79
|
+
const localization = entry.localizations?.[locale];
|
|
80
|
+
if (localization?.stringUnit?.value) {
|
|
81
|
+
entries.push({ key, value: localization.stringUnit.value });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return entries;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get all language codes present in an .xcstrings file
|
|
88
|
+
*
|
|
89
|
+
* @param xcstrings Parsed xcstrings data
|
|
90
|
+
* @returns Set of language codes
|
|
91
|
+
*/
|
|
92
|
+
export function getXCStringsLanguages(xcstrings) {
|
|
93
|
+
const languages = new Set();
|
|
94
|
+
for (const entry of Object.values(xcstrings.strings)) {
|
|
95
|
+
if (entry.localizations) {
|
|
96
|
+
for (const lang of Object.keys(entry.localizations)) {
|
|
97
|
+
languages.add(lang);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return languages;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Update a specific locale's translations in an .xcstrings file
|
|
105
|
+
*
|
|
106
|
+
* This adds or updates translations for a single language without
|
|
107
|
+
* affecting other languages in the file.
|
|
108
|
+
*
|
|
109
|
+
* @param xcstrings Original xcstrings data
|
|
110
|
+
* @param locale Target language code
|
|
111
|
+
* @param translations New translations for the locale
|
|
112
|
+
* @returns Updated xcstrings data
|
|
113
|
+
*/
|
|
114
|
+
export function updateXCStringsLocale(xcstrings, locale, translations) {
|
|
115
|
+
// Deep clone to avoid mutating original
|
|
116
|
+
const result = JSON.parse(JSON.stringify(xcstrings));
|
|
117
|
+
// Create a map for quick lookup
|
|
118
|
+
const translationsMap = new Map();
|
|
119
|
+
for (const { key, value } of translations) {
|
|
120
|
+
translationsMap.set(key, value);
|
|
121
|
+
}
|
|
122
|
+
// Update each string entry
|
|
123
|
+
for (const [key, entry] of Object.entries(result.strings)) {
|
|
124
|
+
const translation = translationsMap.get(key);
|
|
125
|
+
if (translation !== undefined) {
|
|
126
|
+
// Ensure localizations object exists
|
|
127
|
+
if (!entry.localizations) {
|
|
128
|
+
entry.localizations = {};
|
|
129
|
+
}
|
|
130
|
+
// Add or update the locale
|
|
131
|
+
entry.localizations[locale] = {
|
|
132
|
+
stringUnit: {
|
|
133
|
+
state: "translated",
|
|
134
|
+
value: translation,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Merge new translations into an .xcstrings file for a specific locale
|
|
143
|
+
*
|
|
144
|
+
* - Preserves existing translations for the locale that aren't updated
|
|
145
|
+
* - Preserves all other languages completely
|
|
146
|
+
* - Removes keys that no longer exist in source
|
|
147
|
+
* - Updates state to "translated" for new translations
|
|
148
|
+
*
|
|
149
|
+
* @param existing Existing xcstrings data
|
|
150
|
+
* @param locale Target language code
|
|
151
|
+
* @param newTranslations New/updated translations
|
|
152
|
+
* @param sourceKeys Set of all keys in source language
|
|
153
|
+
* @returns Merged xcstrings data
|
|
154
|
+
*/
|
|
155
|
+
export function mergeXCStringsContent(existing, locale, newTranslations, sourceKeys) {
|
|
156
|
+
// Deep clone to avoid mutating original
|
|
157
|
+
const result = JSON.parse(JSON.stringify(existing));
|
|
158
|
+
// Create a map for quick lookup
|
|
159
|
+
const newTranslationsMap = new Map();
|
|
160
|
+
for (const { key, value } of newTranslations) {
|
|
161
|
+
newTranslationsMap.set(key, value);
|
|
162
|
+
}
|
|
163
|
+
// Remove keys that don't exist in source
|
|
164
|
+
for (const key of Object.keys(result.strings)) {
|
|
165
|
+
if (!sourceKeys.has(key)) {
|
|
166
|
+
delete result.strings[key];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Update translations for the target locale
|
|
170
|
+
for (const key of sourceKeys) {
|
|
171
|
+
const entry = result.strings[key];
|
|
172
|
+
if (!entry)
|
|
173
|
+
continue;
|
|
174
|
+
// Get new translation if available
|
|
175
|
+
const newTranslation = newTranslationsMap.get(key);
|
|
176
|
+
if (newTranslation !== undefined) {
|
|
177
|
+
// Ensure localizations object exists
|
|
178
|
+
if (!entry.localizations) {
|
|
179
|
+
entry.localizations = {};
|
|
180
|
+
}
|
|
181
|
+
// Add or update the locale
|
|
182
|
+
entry.localizations[locale] = {
|
|
183
|
+
stringUnit: {
|
|
184
|
+
state: "translated",
|
|
185
|
+
value: newTranslation,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// If no new translation, preserve existing (already in the cloned data)
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Reconstruct .xcstrings JSON content with proper formatting
|
|
195
|
+
*
|
|
196
|
+
* @param xcstrings XCStrings data to serialize
|
|
197
|
+
* @returns Formatted JSON string
|
|
198
|
+
*/
|
|
199
|
+
export function reconstructXCStringsContent(xcstrings) {
|
|
200
|
+
// Xcode uses 2-space indentation
|
|
201
|
+
return JSON.stringify(xcstrings, null, 2) + "\n";
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Create a new empty .xcstrings structure
|
|
205
|
+
*
|
|
206
|
+
* @param sourceLanguage Source language code
|
|
207
|
+
* @returns New empty xcstrings structure
|
|
208
|
+
*/
|
|
209
|
+
export function createEmptyXCStrings(sourceLanguage) {
|
|
210
|
+
return {
|
|
211
|
+
sourceLanguage,
|
|
212
|
+
version: "1.0",
|
|
213
|
+
strings: {},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=xcstrings-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcstrings-parser.js","sourceRoot":"","sources":["../../src/utils/xcstrings-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xcstrings-parser.test.d.ts","sourceRoot":"","sources":["../../src/utils/xcstrings-parser.test.ts"],"names":[],"mappings":""}
|