lingo.dev 0.113.5 → 0.113.7
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/build/cli.cjs +1446 -278
- package/build/cli.cjs.map +1 -1
- package/build/cli.mjs +1338 -170
- package/build/cli.mjs.map +1 -1
- package/package.json +5 -4
package/build/cli.mjs
CHANGED
|
@@ -2151,7 +2151,13 @@ function _isMetadataKey(key) {
|
|
|
2151
2151
|
}
|
|
2152
2152
|
|
|
2153
2153
|
// src/cli/loaders/android.ts
|
|
2154
|
-
import {
|
|
2154
|
+
import { createRequire } from "node:module";
|
|
2155
|
+
import { parseStringPromise } from "xml2js";
|
|
2156
|
+
var require2 = createRequire(import.meta.url);
|
|
2157
|
+
var sax = require2("sax");
|
|
2158
|
+
var defaultAndroidResourcesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
2159
|
+
<resources>
|
|
2160
|
+
</resources>`;
|
|
2155
2161
|
function createAndroidLoader() {
|
|
2156
2162
|
return createLoader({
|
|
2157
2163
|
async pull(locale, input2) {
|
|
@@ -2159,173 +2165,1053 @@ function createAndroidLoader() {
|
|
|
2159
2165
|
if (!input2) {
|
|
2160
2166
|
return {};
|
|
2161
2167
|
}
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2168
|
+
const document = await parseAndroidDocument(input2);
|
|
2169
|
+
return buildPullResult(document);
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
console.error("Error parsing Android resource file:", error);
|
|
2172
|
+
throw new CLIError({
|
|
2173
|
+
message: "Failed to parse Android resource file",
|
|
2174
|
+
docUrl: "androidResouceError"
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
},
|
|
2178
|
+
async push(locale, payload, originalInput, originalLocale, pullInput, pullOutput) {
|
|
2179
|
+
try {
|
|
2180
|
+
const selectedBase = selectBaseXml(
|
|
2181
|
+
locale,
|
|
2182
|
+
originalLocale,
|
|
2183
|
+
pullInput,
|
|
2184
|
+
originalInput
|
|
2185
|
+
);
|
|
2186
|
+
const existingDocument = await parseAndroidDocument(selectedBase);
|
|
2187
|
+
const sourceDocument = await parseAndroidDocument(originalInput);
|
|
2188
|
+
const translatedDocument = buildTranslatedDocument(
|
|
2189
|
+
payload,
|
|
2190
|
+
existingDocument,
|
|
2191
|
+
sourceDocument
|
|
2192
|
+
);
|
|
2193
|
+
const referenceXml = selectedBase || originalInput || defaultAndroidResourcesXml;
|
|
2194
|
+
const declaration = resolveXmlDeclaration(referenceXml);
|
|
2195
|
+
return buildAndroidXml(translatedDocument, declaration);
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
console.error("Error generating Android resource file:", error);
|
|
2198
|
+
throw new CLIError({
|
|
2199
|
+
message: "Failed to generate Android resource file",
|
|
2200
|
+
docUrl: "androidResouceError"
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
function resolveXmlDeclaration(xml) {
|
|
2207
|
+
if (!xml) {
|
|
2208
|
+
const xmldec = {
|
|
2209
|
+
version: "1.0",
|
|
2210
|
+
encoding: "utf-8"
|
|
2211
|
+
};
|
|
2212
|
+
return {
|
|
2213
|
+
xmldec,
|
|
2214
|
+
headless: false
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
const match2 = xml.match(
|
|
2218
|
+
/<\?xml\s+version="([^"]+)"(?:\s+encoding="([^"]+)")?\s*\?>/
|
|
2219
|
+
);
|
|
2220
|
+
if (match2) {
|
|
2221
|
+
const version = match2[1] && match2[1].trim().length > 0 ? match2[1] : "1.0";
|
|
2222
|
+
const encoding = match2[2] && match2[2].trim().length > 0 ? match2[2] : void 0;
|
|
2223
|
+
const xmldec = encoding ? { version, encoding } : { version };
|
|
2224
|
+
return {
|
|
2225
|
+
xmldec,
|
|
2226
|
+
headless: false
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
return { headless: true };
|
|
2230
|
+
}
|
|
2231
|
+
async function parseAndroidDocument(input2) {
|
|
2232
|
+
const xmlToParse = input2 && input2.trim().length > 0 ? input2 : defaultAndroidResourcesXml;
|
|
2233
|
+
const parsed = await parseStringPromise(xmlToParse, {
|
|
2234
|
+
explicitArray: true,
|
|
2235
|
+
explicitChildren: true,
|
|
2236
|
+
preserveChildrenOrder: true,
|
|
2237
|
+
charsAsChildren: true,
|
|
2238
|
+
includeWhiteChars: true,
|
|
2239
|
+
mergeAttrs: false,
|
|
2240
|
+
normalize: false,
|
|
2241
|
+
normalizeTags: false,
|
|
2242
|
+
trim: false,
|
|
2243
|
+
attrkey: "$",
|
|
2244
|
+
charkey: "_",
|
|
2245
|
+
childkey: "$$"
|
|
2246
|
+
});
|
|
2247
|
+
if (!parsed || !parsed.resources) {
|
|
2248
|
+
return {
|
|
2249
|
+
resources: { $$: [] },
|
|
2250
|
+
resourceNodes: []
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
const resourcesNode = parsed.resources;
|
|
2254
|
+
resourcesNode["#name"] = resourcesNode["#name"] ?? "resources";
|
|
2255
|
+
resourcesNode.$$ = resourcesNode.$$ ?? [];
|
|
2256
|
+
const metadata = extractResourceMetadata(xmlToParse);
|
|
2257
|
+
const resourceNodes = [];
|
|
2258
|
+
let metaIndex = 0;
|
|
2259
|
+
for (const child of resourcesNode.$$) {
|
|
2260
|
+
const elementName = child?.["#name"];
|
|
2261
|
+
if (!isResourceElementName(elementName)) {
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const meta = metadata[metaIndex++];
|
|
2265
|
+
if (!meta || meta.type !== elementName) {
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
const name = child?.$?.name ?? meta.name;
|
|
2269
|
+
if (!name) {
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
const translatable = (child?.$?.translatable ?? "").toLowerCase() !== "false";
|
|
2273
|
+
switch (meta.type) {
|
|
2274
|
+
case "string": {
|
|
2275
|
+
resourceNodes.push({
|
|
2276
|
+
type: "string",
|
|
2277
|
+
name,
|
|
2278
|
+
translatable,
|
|
2279
|
+
node: child,
|
|
2280
|
+
meta: cloneTextMeta(meta.meta)
|
|
2281
|
+
});
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2284
|
+
case "string-array": {
|
|
2285
|
+
const itemNodes = child?.item ?? [];
|
|
2286
|
+
const items = [];
|
|
2287
|
+
const templateItems = meta.items;
|
|
2288
|
+
for (let i = 0; i < Math.max(itemNodes.length, templateItems.length); i++) {
|
|
2289
|
+
const nodeItem = itemNodes[i];
|
|
2290
|
+
const templateItem = templateItems[i] ?? templateItems[templateItems.length - 1];
|
|
2291
|
+
if (!nodeItem) {
|
|
2170
2292
|
continue;
|
|
2171
2293
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2294
|
+
items.push({
|
|
2295
|
+
node: nodeItem,
|
|
2296
|
+
meta: cloneTextMeta(templateItem.meta)
|
|
2297
|
+
});
|
|
2176
2298
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
attrkey: "$",
|
|
2184
|
-
charkey: "_"
|
|
2299
|
+
resourceNodes.push({
|
|
2300
|
+
type: "string-array",
|
|
2301
|
+
name,
|
|
2302
|
+
translatable,
|
|
2303
|
+
node: child,
|
|
2304
|
+
items
|
|
2185
2305
|
});
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
itemValue === "" || /^\s+$/.test(itemValue) ? itemValue : itemValue.trim()
|
|
2208
|
-
);
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
|
-
result[name] = items;
|
|
2306
|
+
break;
|
|
2307
|
+
}
|
|
2308
|
+
case "plurals": {
|
|
2309
|
+
const itemNodes = child?.item ?? [];
|
|
2310
|
+
const templateItems = meta.items;
|
|
2311
|
+
const items = [];
|
|
2312
|
+
for (const templateItem of templateItems) {
|
|
2313
|
+
const quantity = templateItem.quantity;
|
|
2314
|
+
if (!quantity) {
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
const nodeItem = itemNodes.find(
|
|
2318
|
+
(item) => item?.$?.quantity === quantity
|
|
2319
|
+
);
|
|
2320
|
+
if (!nodeItem) {
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
items.push({
|
|
2324
|
+
node: nodeItem,
|
|
2325
|
+
quantity,
|
|
2326
|
+
meta: cloneTextMeta(templateItem.meta)
|
|
2212
2327
|
});
|
|
2213
2328
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2329
|
+
resourceNodes.push({
|
|
2330
|
+
type: "plurals",
|
|
2331
|
+
name,
|
|
2332
|
+
translatable,
|
|
2333
|
+
node: child,
|
|
2334
|
+
items
|
|
2335
|
+
});
|
|
2336
|
+
break;
|
|
2337
|
+
}
|
|
2338
|
+
case "bool": {
|
|
2339
|
+
resourceNodes.push({
|
|
2340
|
+
type: "bool",
|
|
2341
|
+
name,
|
|
2342
|
+
translatable,
|
|
2343
|
+
node: child,
|
|
2344
|
+
meta: cloneTextMeta(meta.meta)
|
|
2345
|
+
});
|
|
2346
|
+
break;
|
|
2347
|
+
}
|
|
2348
|
+
case "integer": {
|
|
2349
|
+
resourceNodes.push({
|
|
2350
|
+
type: "integer",
|
|
2351
|
+
name,
|
|
2352
|
+
translatable,
|
|
2353
|
+
node: child,
|
|
2354
|
+
meta: cloneTextMeta(meta.meta)
|
|
2355
|
+
});
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return { resources: resourcesNode, resourceNodes };
|
|
2361
|
+
}
|
|
2362
|
+
function buildPullResult(document) {
|
|
2363
|
+
const result = {};
|
|
2364
|
+
for (const resource of document.resourceNodes) {
|
|
2365
|
+
if (!resource.translatable) {
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
switch (resource.type) {
|
|
2369
|
+
case "string": {
|
|
2370
|
+
result[resource.name] = decodeAndroidText(
|
|
2371
|
+
segmentsToString(resource.meta.segments)
|
|
2372
|
+
);
|
|
2373
|
+
break;
|
|
2374
|
+
}
|
|
2375
|
+
case "string-array": {
|
|
2376
|
+
result[resource.name] = resource.items.map(
|
|
2377
|
+
(item) => decodeAndroidText(segmentsToString(item.meta.segments))
|
|
2378
|
+
);
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
case "plurals": {
|
|
2382
|
+
const pluralMap = {};
|
|
2383
|
+
for (const item of resource.items) {
|
|
2384
|
+
pluralMap[item.quantity] = decodeAndroidText(
|
|
2385
|
+
segmentsToString(item.meta.segments)
|
|
2386
|
+
);
|
|
2236
2387
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2388
|
+
result[resource.name] = pluralMap;
|
|
2389
|
+
break;
|
|
2390
|
+
}
|
|
2391
|
+
case "bool": {
|
|
2392
|
+
const value = segmentsToString(resource.meta.segments).trim();
|
|
2393
|
+
result[resource.name] = value === "true";
|
|
2394
|
+
break;
|
|
2395
|
+
}
|
|
2396
|
+
case "integer": {
|
|
2397
|
+
const value = parseInt(
|
|
2398
|
+
segmentsToString(resource.meta.segments).trim(),
|
|
2399
|
+
10
|
|
2400
|
+
);
|
|
2401
|
+
result[resource.name] = Number.isNaN(value) ? 0 : value;
|
|
2402
|
+
break;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return result;
|
|
2407
|
+
}
|
|
2408
|
+
function buildTranslatedDocument(payload, existingDocument, sourceDocument) {
|
|
2409
|
+
const templateDocument = sourceDocument;
|
|
2410
|
+
const finalDocument = cloneDocumentStructure(templateDocument);
|
|
2411
|
+
const templateMap = createResourceMap(templateDocument);
|
|
2412
|
+
const existingMap = createResourceMap(existingDocument);
|
|
2413
|
+
const payloadEntries = payload ?? {};
|
|
2414
|
+
const finalMap = createResourceMap(finalDocument);
|
|
2415
|
+
for (const resource of finalDocument.resourceNodes) {
|
|
2416
|
+
if (!resource.translatable) {
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
const templateResource = templateMap.get(resource.name);
|
|
2420
|
+
let translationValue;
|
|
2421
|
+
if (Object.prototype.hasOwnProperty.call(payloadEntries, resource.name) && payloadEntries[resource.name] !== void 0 && payloadEntries[resource.name] !== null) {
|
|
2422
|
+
translationValue = payloadEntries[resource.name];
|
|
2423
|
+
} else if (existingMap.has(resource.name)) {
|
|
2424
|
+
translationValue = extractValueFromResource(
|
|
2425
|
+
existingMap.get(resource.name)
|
|
2426
|
+
);
|
|
2427
|
+
} else {
|
|
2428
|
+
translationValue = extractValueFromResource(templateResource ?? resource);
|
|
2429
|
+
}
|
|
2430
|
+
updateResourceNode(resource, translationValue, templateResource);
|
|
2431
|
+
}
|
|
2432
|
+
for (const resource of existingDocument.resourceNodes) {
|
|
2433
|
+
if (finalMap.has(resource.name)) {
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
const cloned = cloneResourceNode(resource);
|
|
2437
|
+
appendResourceNode(finalDocument, cloned);
|
|
2438
|
+
finalMap.set(cloned.name, cloned);
|
|
2439
|
+
}
|
|
2440
|
+
for (const [name, value] of Object.entries(payloadEntries)) {
|
|
2441
|
+
if (finalMap.has(name)) {
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
const inferred = createResourceNodeFromValue(name, value);
|
|
2446
|
+
appendResourceNode(finalDocument, inferred);
|
|
2447
|
+
finalMap.set(name, inferred);
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
if (error instanceof CLIError) {
|
|
2450
|
+
throw error;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return finalDocument;
|
|
2455
|
+
}
|
|
2456
|
+
function buildAndroidXml(document, declaration) {
|
|
2457
|
+
const xmlBody = serializeElement(document.resources);
|
|
2458
|
+
if (declaration.headless) {
|
|
2459
|
+
return xmlBody;
|
|
2460
|
+
}
|
|
2461
|
+
if (declaration.xmldec) {
|
|
2462
|
+
const { version, encoding } = declaration.xmldec;
|
|
2463
|
+
const encodingPart = encoding ? ` encoding="${encoding}"` : "";
|
|
2464
|
+
return `<?xml version="${version}"${encodingPart}?>
|
|
2465
|
+
${xmlBody}`;
|
|
2466
|
+
}
|
|
2467
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
2468
|
+
${xmlBody}`;
|
|
2469
|
+
}
|
|
2470
|
+
function selectBaseXml(locale, originalLocale, pullInput, originalInput) {
|
|
2471
|
+
if (locale === originalLocale) {
|
|
2472
|
+
return pullInput ?? originalInput;
|
|
2473
|
+
}
|
|
2474
|
+
return pullInput ?? originalInput;
|
|
2475
|
+
}
|
|
2476
|
+
function updateResourceNode(target, rawValue, template) {
|
|
2477
|
+
switch (target.type) {
|
|
2478
|
+
case "string": {
|
|
2479
|
+
const value = asString(rawValue, target.name);
|
|
2480
|
+
const templateMeta = template && template.type === "string" ? template.meta : target.meta;
|
|
2481
|
+
const useCdata = templateMeta.hasCdata;
|
|
2482
|
+
setTextualNodeContent(target.node, value, useCdata);
|
|
2483
|
+
target.meta = makeTextMeta([
|
|
2484
|
+
{ kind: useCdata ? "cdata" : "text", value }
|
|
2485
|
+
]);
|
|
2486
|
+
break;
|
|
2487
|
+
}
|
|
2488
|
+
case "string-array": {
|
|
2489
|
+
const values = asStringArray(rawValue, target.name);
|
|
2490
|
+
const templateItems = template && template.type === "string-array" ? template.items : target.items;
|
|
2491
|
+
const maxLength = Math.max(target.items.length, templateItems.length);
|
|
2492
|
+
for (let index = 0; index < maxLength; index++) {
|
|
2493
|
+
const targetItem = target.items[index];
|
|
2494
|
+
const templateItem = templateItems[index] ?? templateItems[templateItems.length - 1] ?? target.items[index];
|
|
2495
|
+
if (!targetItem || !templateItem) {
|
|
2496
|
+
continue;
|
|
2245
2497
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2498
|
+
const translation = index < values.length ? values[index] : segmentsToString(templateItem.meta.segments);
|
|
2499
|
+
const useCdata = templateItem.meta.hasCdata;
|
|
2500
|
+
setTextualNodeContent(targetItem.node, translation, useCdata);
|
|
2501
|
+
targetItem.meta = makeTextMeta([
|
|
2502
|
+
{ kind: useCdata ? "cdata" : "text", value: translation }
|
|
2503
|
+
]);
|
|
2504
|
+
}
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
case "plurals": {
|
|
2508
|
+
const pluralValues = asPluralMap(rawValue, target.name);
|
|
2509
|
+
const templateItems = template && template.type === "plurals" ? template.items : target.items;
|
|
2510
|
+
const templateMap = new Map(
|
|
2511
|
+
templateItems.map((item) => [item.quantity, item])
|
|
2512
|
+
);
|
|
2513
|
+
for (const item of target.items) {
|
|
2514
|
+
const templateItem = templateMap.get(item.quantity) ?? templateMap.values().next().value;
|
|
2515
|
+
const fallback = templateItem ? segmentsToString(templateItem.meta.segments) : segmentsToString(item.meta.segments);
|
|
2516
|
+
const translation = typeof pluralValues[item.quantity] === "string" ? pluralValues[item.quantity] : fallback;
|
|
2517
|
+
const useCdata = templateItem ? templateItem.meta.hasCdata : item.meta.hasCdata;
|
|
2518
|
+
setTextualNodeContent(item.node, translation, useCdata);
|
|
2519
|
+
item.meta = makeTextMeta([
|
|
2520
|
+
{ kind: useCdata ? "cdata" : "text", value: translation }
|
|
2521
|
+
]);
|
|
2522
|
+
}
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
case "bool": {
|
|
2526
|
+
const boolValue = asBoolean(rawValue, target.name);
|
|
2527
|
+
const strValue = boolValue ? "true" : "false";
|
|
2528
|
+
setTextualNodeContent(target.node, strValue, false);
|
|
2529
|
+
target.meta = makeTextMeta([{ kind: "text", value: strValue }]);
|
|
2530
|
+
break;
|
|
2531
|
+
}
|
|
2532
|
+
case "integer": {
|
|
2533
|
+
const intValue = asInteger(rawValue, target.name);
|
|
2534
|
+
const strValue = intValue.toString();
|
|
2535
|
+
setTextualNodeContent(target.node, strValue, false);
|
|
2536
|
+
target.meta = makeTextMeta([{ kind: "text", value: strValue }]);
|
|
2537
|
+
break;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
function appendResourceNode(document, resourceNode) {
|
|
2542
|
+
document.resources.$$ = document.resources.$$ ?? [];
|
|
2543
|
+
const children = document.resources.$$;
|
|
2544
|
+
if (children.length === 0 || children[children.length - 1]["#name"] !== "__text__" && children[children.length - 1]["#name"] !== "__comment__") {
|
|
2545
|
+
children.push({ "#name": "__text__", _: "\n " });
|
|
2546
|
+
}
|
|
2547
|
+
children.push(resourceNode.node);
|
|
2548
|
+
children.push({ "#name": "__text__", _: "\n" });
|
|
2549
|
+
document.resourceNodes.push(resourceNode);
|
|
2550
|
+
}
|
|
2551
|
+
function setTextualNodeContent(node, value, useCdata) {
|
|
2552
|
+
const escapedValue = useCdata ? value : escapeAndroidString(value);
|
|
2553
|
+
node._ = escapedValue;
|
|
2554
|
+
node.$$ = node.$$ ?? [];
|
|
2555
|
+
let textNode = node.$$.find(
|
|
2556
|
+
(child) => child["#name"] === "__text__" || child["#name"] === "__cdata"
|
|
2557
|
+
);
|
|
2558
|
+
if (!textNode) {
|
|
2559
|
+
textNode = {};
|
|
2560
|
+
node.$$.push(textNode);
|
|
2561
|
+
}
|
|
2562
|
+
textNode["#name"] = useCdata ? "__cdata" : "__text__";
|
|
2563
|
+
textNode._ = useCdata ? value : escapedValue;
|
|
2564
|
+
}
|
|
2565
|
+
function buildResourceNameMap(document) {
|
|
2566
|
+
const map = /* @__PURE__ */ new Map();
|
|
2567
|
+
for (const node of document.resourceNodes) {
|
|
2568
|
+
if (!map.has(node.name)) {
|
|
2569
|
+
map.set(node.name, node);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
return map;
|
|
2573
|
+
}
|
|
2574
|
+
function createResourceMap(document) {
|
|
2575
|
+
return buildResourceNameMap(document);
|
|
2576
|
+
}
|
|
2577
|
+
function cloneResourceNode(resource) {
|
|
2578
|
+
switch (resource.type) {
|
|
2579
|
+
case "string": {
|
|
2580
|
+
const nodeClone = deepClone(resource.node);
|
|
2581
|
+
return {
|
|
2582
|
+
type: "string",
|
|
2583
|
+
name: resource.name,
|
|
2584
|
+
translatable: resource.translatable,
|
|
2585
|
+
node: nodeClone,
|
|
2586
|
+
meta: cloneTextMeta(resource.meta)
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
case "string-array": {
|
|
2590
|
+
const nodeClone = deepClone(resource.node);
|
|
2591
|
+
const itemNodes = nodeClone.item ?? [];
|
|
2592
|
+
const items = itemNodes.map((itemNode, index) => {
|
|
2593
|
+
const templateMeta = resource.items[index]?.meta ?? resource.items[resource.items.length - 1]?.meta ?? makeTextMeta([]);
|
|
2594
|
+
return {
|
|
2595
|
+
node: itemNode,
|
|
2596
|
+
meta: cloneTextMeta(templateMeta)
|
|
2597
|
+
};
|
|
2598
|
+
});
|
|
2599
|
+
return {
|
|
2600
|
+
type: "string-array",
|
|
2601
|
+
name: resource.name,
|
|
2602
|
+
translatable: resource.translatable,
|
|
2603
|
+
node: nodeClone,
|
|
2604
|
+
items
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
case "plurals": {
|
|
2608
|
+
const nodeClone = deepClone(resource.node);
|
|
2609
|
+
const itemNodes = nodeClone.item ?? [];
|
|
2610
|
+
const items = [];
|
|
2611
|
+
for (const templateItem of resource.items) {
|
|
2612
|
+
const cloneNode = itemNodes.find(
|
|
2613
|
+
(item) => item?.$?.quantity === templateItem.quantity
|
|
2614
|
+
);
|
|
2615
|
+
if (!cloneNode) {
|
|
2616
|
+
continue;
|
|
2254
2617
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2618
|
+
items.push({
|
|
2619
|
+
node: cloneNode,
|
|
2620
|
+
quantity: templateItem.quantity,
|
|
2621
|
+
meta: cloneTextMeta(templateItem.meta)
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
return {
|
|
2625
|
+
type: "plurals",
|
|
2626
|
+
name: resource.name,
|
|
2627
|
+
translatable: resource.translatable,
|
|
2628
|
+
node: nodeClone,
|
|
2629
|
+
items
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
case "bool": {
|
|
2633
|
+
const nodeClone = deepClone(resource.node);
|
|
2634
|
+
return {
|
|
2635
|
+
type: "bool",
|
|
2636
|
+
name: resource.name,
|
|
2637
|
+
translatable: resource.translatable,
|
|
2638
|
+
node: nodeClone,
|
|
2639
|
+
meta: cloneTextMeta(resource.meta)
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
case "integer": {
|
|
2643
|
+
const nodeClone = deepClone(resource.node);
|
|
2644
|
+
return {
|
|
2645
|
+
type: "integer",
|
|
2646
|
+
name: resource.name,
|
|
2647
|
+
translatable: resource.translatable,
|
|
2648
|
+
node: nodeClone,
|
|
2649
|
+
meta: cloneTextMeta(resource.meta)
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function cloneTextMeta(meta) {
|
|
2655
|
+
return {
|
|
2656
|
+
hasCdata: meta.hasCdata,
|
|
2657
|
+
segments: meta.segments.map((segment) => ({ ...segment }))
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
function asString(value, name) {
|
|
2661
|
+
if (typeof value === "string") {
|
|
2662
|
+
return value;
|
|
2663
|
+
}
|
|
2664
|
+
throw new CLIError({
|
|
2665
|
+
message: `Expected string value for resource "${name}"`,
|
|
2666
|
+
docUrl: "androidResouceError"
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
function asStringArray(value, name) {
|
|
2670
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
2671
|
+
return value;
|
|
2672
|
+
}
|
|
2673
|
+
throw new CLIError({
|
|
2674
|
+
message: `Expected array of strings for resource "${name}"`,
|
|
2675
|
+
docUrl: "androidResouceError"
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
function asPluralMap(value, name) {
|
|
2679
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2680
|
+
const result = {};
|
|
2681
|
+
for (const [quantity, pluralValue] of Object.entries(value)) {
|
|
2682
|
+
if (typeof pluralValue !== "string") {
|
|
2258
2683
|
throw new CLIError({
|
|
2259
|
-
message: "
|
|
2684
|
+
message: `Expected plural item "${quantity}" of "${name}" to be a string`,
|
|
2260
2685
|
docUrl: "androidResouceError"
|
|
2261
2686
|
});
|
|
2262
2687
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2688
|
+
result[quantity] = pluralValue;
|
|
2689
|
+
}
|
|
2690
|
+
return result;
|
|
2691
|
+
}
|
|
2692
|
+
throw new CLIError({
|
|
2693
|
+
message: `Expected object value for plurals resource "${name}"`,
|
|
2694
|
+
docUrl: "androidResouceError"
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
function asBoolean(value, name) {
|
|
2698
|
+
if (typeof value === "boolean") {
|
|
2699
|
+
return value;
|
|
2700
|
+
}
|
|
2701
|
+
if (typeof value === "string") {
|
|
2702
|
+
if (value === "true" || value === "false") {
|
|
2703
|
+
return value === "true";
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
throw new CLIError({
|
|
2707
|
+
message: `Expected boolean value for resource "${name}"`,
|
|
2708
|
+
docUrl: "androidResouceError"
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
function asInteger(value, name) {
|
|
2712
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
2713
|
+
return value;
|
|
2714
|
+
}
|
|
2715
|
+
throw new CLIError({
|
|
2716
|
+
message: `Expected number value for resource "${name}"`,
|
|
2717
|
+
docUrl: "androidResouceError"
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
function escapeAndroidString(value) {
|
|
2721
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/(?<!\\)'/g, "\\'");
|
|
2722
|
+
}
|
|
2723
|
+
function segmentsToString(segments) {
|
|
2724
|
+
return segments.map((segment) => segment.value).join("");
|
|
2725
|
+
}
|
|
2726
|
+
function makeTextMeta(segments) {
|
|
2727
|
+
return {
|
|
2728
|
+
segments,
|
|
2729
|
+
hasCdata: segments.some((segment) => segment.kind === "cdata")
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
function createResourceNodeFromValue(name, value) {
|
|
2733
|
+
const inferredType = inferTypeFromValue(value);
|
|
2734
|
+
switch (inferredType) {
|
|
2735
|
+
case "string": {
|
|
2736
|
+
const stringValue = asString(value, name);
|
|
2737
|
+
const escaped = escapeAndroidString(stringValue);
|
|
2738
|
+
const node = {
|
|
2739
|
+
"#name": "string",
|
|
2740
|
+
$: { name },
|
|
2741
|
+
_: escaped,
|
|
2742
|
+
$$: [{ "#name": "__text__", _: escaped }]
|
|
2743
|
+
};
|
|
2744
|
+
return {
|
|
2745
|
+
type: "string",
|
|
2746
|
+
name,
|
|
2747
|
+
translatable: true,
|
|
2748
|
+
node,
|
|
2749
|
+
meta: makeTextMeta([{ kind: "text", value: stringValue }])
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
case "string-array": {
|
|
2753
|
+
const items = asStringArray(value, name);
|
|
2754
|
+
const node = {
|
|
2755
|
+
"#name": "string-array",
|
|
2756
|
+
$: { name },
|
|
2757
|
+
$$: [],
|
|
2758
|
+
item: []
|
|
2759
|
+
};
|
|
2760
|
+
const itemNodes = [];
|
|
2761
|
+
for (const itemValue of items) {
|
|
2762
|
+
const escaped = escapeAndroidString(itemValue);
|
|
2763
|
+
const itemNode = {
|
|
2764
|
+
"#name": "item",
|
|
2765
|
+
_: escaped,
|
|
2766
|
+
$$: [{ "#name": "__text__", _: escaped }]
|
|
2274
2767
|
};
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2768
|
+
node.$$.push(itemNode);
|
|
2769
|
+
node.item.push(itemNode);
|
|
2770
|
+
itemNodes.push({
|
|
2771
|
+
node: itemNode,
|
|
2772
|
+
meta: makeTextMeta([{ kind: "text", value: itemValue }])
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
return {
|
|
2776
|
+
type: "string-array",
|
|
2777
|
+
name,
|
|
2778
|
+
translatable: true,
|
|
2779
|
+
node,
|
|
2780
|
+
items: itemNodes
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
case "plurals": {
|
|
2784
|
+
const pluralMap = asPluralMap(value, name);
|
|
2785
|
+
const node = {
|
|
2786
|
+
"#name": "plurals",
|
|
2787
|
+
$: { name },
|
|
2788
|
+
$$: [],
|
|
2789
|
+
item: []
|
|
2790
|
+
};
|
|
2791
|
+
const items = [];
|
|
2792
|
+
for (const [quantity, pluralValue] of Object.entries(pluralMap)) {
|
|
2793
|
+
const escaped = escapeAndroidString(pluralValue);
|
|
2794
|
+
const itemNode = {
|
|
2795
|
+
"#name": "item",
|
|
2796
|
+
$: { quantity },
|
|
2797
|
+
_: escaped,
|
|
2798
|
+
$$: [{ "#name": "__text__", _: escaped }]
|
|
2279
2799
|
};
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2800
|
+
node.$$.push(itemNode);
|
|
2801
|
+
node.item.push(itemNode);
|
|
2802
|
+
items.push({
|
|
2803
|
+
node: itemNode,
|
|
2804
|
+
quantity,
|
|
2805
|
+
meta: makeTextMeta([{ kind: "text", value: pluralValue }])
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
return {
|
|
2809
|
+
type: "plurals",
|
|
2810
|
+
name,
|
|
2811
|
+
translatable: true,
|
|
2812
|
+
node,
|
|
2813
|
+
items
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
case "bool": {
|
|
2817
|
+
const boolValue = asBoolean(value, name);
|
|
2818
|
+
const textValue = boolValue ? "true" : "false";
|
|
2819
|
+
const node = {
|
|
2820
|
+
"#name": "bool",
|
|
2821
|
+
$: { name },
|
|
2822
|
+
_: textValue,
|
|
2823
|
+
$$: [{ "#name": "__text__", _: textValue }]
|
|
2824
|
+
};
|
|
2825
|
+
return {
|
|
2826
|
+
type: "bool",
|
|
2827
|
+
name,
|
|
2828
|
+
translatable: true,
|
|
2829
|
+
node,
|
|
2830
|
+
meta: makeTextMeta([{ kind: "text", value: textValue }])
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
case "integer": {
|
|
2834
|
+
const intValue = asInteger(value, name);
|
|
2835
|
+
const textValue = intValue.toString();
|
|
2836
|
+
const node = {
|
|
2837
|
+
"#name": "integer",
|
|
2838
|
+
$: { name },
|
|
2839
|
+
_: textValue,
|
|
2840
|
+
$$: [{ "#name": "__text__", _: textValue }]
|
|
2841
|
+
};
|
|
2842
|
+
return {
|
|
2843
|
+
type: "integer",
|
|
2844
|
+
name,
|
|
2845
|
+
translatable: true,
|
|
2846
|
+
node,
|
|
2847
|
+
meta: makeTextMeta([{ kind: "text", value: textValue }])
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function cloneDocumentStructure(document) {
|
|
2853
|
+
const resourcesClone = deepClone(document.resources);
|
|
2854
|
+
const lookup = buildResourceLookup(resourcesClone);
|
|
2855
|
+
const resourceNodes = [];
|
|
2856
|
+
for (const resource of document.resourceNodes) {
|
|
2857
|
+
const cloned = cloneResourceNodeFromLookup(resource, lookup);
|
|
2858
|
+
resourceNodes.push(cloned);
|
|
2859
|
+
}
|
|
2860
|
+
return {
|
|
2861
|
+
resources: resourcesClone,
|
|
2862
|
+
resourceNodes
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
function buildResourceLookup(resources) {
|
|
2866
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
2867
|
+
const children = Array.isArray(resources.$$) ? resources.$$ : [];
|
|
2868
|
+
for (const child of children) {
|
|
2869
|
+
const type = child?.["#name"];
|
|
2870
|
+
const name = child?.$?.name;
|
|
2871
|
+
if (!type || !name || !isResourceElementName(type)) {
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const key = resourceLookupKey(type, name);
|
|
2875
|
+
if (!lookup.has(key)) {
|
|
2876
|
+
lookup.set(key, []);
|
|
2877
|
+
}
|
|
2878
|
+
lookup.get(key).push(child);
|
|
2879
|
+
}
|
|
2880
|
+
return lookup;
|
|
2881
|
+
}
|
|
2882
|
+
function cloneResourceNodeFromLookup(resource, lookup) {
|
|
2883
|
+
const node = takeResourceNode(lookup, resource.type, resource.name);
|
|
2884
|
+
if (!node) {
|
|
2885
|
+
return cloneResourceNode(resource);
|
|
2886
|
+
}
|
|
2887
|
+
switch (resource.type) {
|
|
2888
|
+
case "string": {
|
|
2889
|
+
return {
|
|
2890
|
+
type: "string",
|
|
2891
|
+
name: resource.name,
|
|
2892
|
+
translatable: resource.translatable,
|
|
2893
|
+
node,
|
|
2894
|
+
meta: cloneTextMeta(resource.meta)
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
case "string-array": {
|
|
2898
|
+
const childItems = (Array.isArray(node.$$) ? node.$$ : []).filter(
|
|
2899
|
+
(child) => child?.["#name"] === "item"
|
|
2900
|
+
);
|
|
2901
|
+
node.item = childItems;
|
|
2902
|
+
if (childItems.length < resource.items.length) {
|
|
2903
|
+
return cloneResourceNode(resource);
|
|
2904
|
+
}
|
|
2905
|
+
const items = resource.items.map((item, index) => {
|
|
2906
|
+
const nodeItem = childItems[index];
|
|
2907
|
+
if (!nodeItem) {
|
|
2908
|
+
return {
|
|
2909
|
+
node: deepClone(item.node),
|
|
2910
|
+
meta: cloneTextMeta(item.meta)
|
|
2911
|
+
};
|
|
2310
2912
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2913
|
+
return {
|
|
2914
|
+
node: nodeItem,
|
|
2915
|
+
meta: cloneTextMeta(item.meta)
|
|
2916
|
+
};
|
|
2917
|
+
});
|
|
2918
|
+
return {
|
|
2919
|
+
type: "string-array",
|
|
2920
|
+
name: resource.name,
|
|
2921
|
+
translatable: resource.translatable,
|
|
2922
|
+
node,
|
|
2923
|
+
items
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
case "plurals": {
|
|
2927
|
+
const childItems = (Array.isArray(node.$$) ? node.$$ : []).filter(
|
|
2928
|
+
(child) => child?.["#name"] === "item"
|
|
2929
|
+
);
|
|
2930
|
+
node.item = childItems;
|
|
2931
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
2932
|
+
for (const item of childItems) {
|
|
2933
|
+
if (item?.$?.quantity) {
|
|
2934
|
+
itemMap.set(item.$.quantity, item);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
const items = [];
|
|
2938
|
+
for (const templateItem of resource.items) {
|
|
2939
|
+
const nodeItem = itemMap.get(templateItem.quantity);
|
|
2940
|
+
if (!nodeItem) {
|
|
2941
|
+
return cloneResourceNode(resource);
|
|
2942
|
+
}
|
|
2943
|
+
items.push({
|
|
2944
|
+
node: nodeItem,
|
|
2945
|
+
quantity: templateItem.quantity,
|
|
2946
|
+
meta: cloneTextMeta(templateItem.meta)
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
return {
|
|
2950
|
+
type: "plurals",
|
|
2951
|
+
name: resource.name,
|
|
2952
|
+
translatable: resource.translatable,
|
|
2953
|
+
node,
|
|
2954
|
+
items
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
case "bool": {
|
|
2958
|
+
return {
|
|
2959
|
+
type: "bool",
|
|
2960
|
+
name: resource.name,
|
|
2961
|
+
translatable: resource.translatable,
|
|
2962
|
+
node,
|
|
2963
|
+
meta: cloneTextMeta(resource.meta)
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
case "integer": {
|
|
2967
|
+
return {
|
|
2968
|
+
type: "integer",
|
|
2969
|
+
name: resource.name,
|
|
2970
|
+
translatable: resource.translatable,
|
|
2971
|
+
node,
|
|
2972
|
+
meta: cloneTextMeta(resource.meta)
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
function takeResourceNode(lookup, type, name) {
|
|
2978
|
+
const key = resourceLookupKey(type, name);
|
|
2979
|
+
const list = lookup.get(key);
|
|
2980
|
+
if (!list || list.length === 0) {
|
|
2981
|
+
return void 0;
|
|
2982
|
+
}
|
|
2983
|
+
return list.shift();
|
|
2984
|
+
}
|
|
2985
|
+
function resourceLookupKey(type, name) {
|
|
2986
|
+
return `${type}:${name}`;
|
|
2987
|
+
}
|
|
2988
|
+
function extractValueFromResource(resource) {
|
|
2989
|
+
switch (resource.type) {
|
|
2990
|
+
case "string":
|
|
2991
|
+
return decodeAndroidText(segmentsToString(resource.meta.segments));
|
|
2992
|
+
case "string-array":
|
|
2993
|
+
return resource.items.map(
|
|
2994
|
+
(item) => decodeAndroidText(segmentsToString(item.meta.segments))
|
|
2995
|
+
);
|
|
2996
|
+
case "plurals": {
|
|
2997
|
+
const result = {};
|
|
2998
|
+
for (const item of resource.items) {
|
|
2999
|
+
result[item.quantity] = decodeAndroidText(
|
|
3000
|
+
segmentsToString(item.meta.segments)
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
return result;
|
|
3004
|
+
}
|
|
3005
|
+
case "bool": {
|
|
3006
|
+
const value = segmentsToString(resource.meta.segments).trim();
|
|
3007
|
+
return value === "true";
|
|
3008
|
+
}
|
|
3009
|
+
case "integer": {
|
|
3010
|
+
const value = parseInt(
|
|
3011
|
+
segmentsToString(resource.meta.segments).trim(),
|
|
3012
|
+
10
|
|
3013
|
+
);
|
|
3014
|
+
return Number.isNaN(value) ? 0 : value;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
function inferTypeFromValue(value) {
|
|
3019
|
+
if (typeof value === "string") {
|
|
3020
|
+
return "string";
|
|
3021
|
+
}
|
|
3022
|
+
if (Array.isArray(value)) {
|
|
3023
|
+
return "string-array";
|
|
3024
|
+
}
|
|
3025
|
+
if (value && typeof value === "object") {
|
|
3026
|
+
return "plurals";
|
|
3027
|
+
}
|
|
3028
|
+
if (typeof value === "boolean") {
|
|
3029
|
+
return "bool";
|
|
3030
|
+
}
|
|
3031
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
3032
|
+
return "integer";
|
|
3033
|
+
}
|
|
3034
|
+
throw new CLIError({
|
|
3035
|
+
message: "Unable to infer Android resource type from payload",
|
|
3036
|
+
docUrl: "androidResouceError"
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
function extractResourceMetadata(xml) {
|
|
3040
|
+
const parser = sax.parser(true, {
|
|
3041
|
+
trim: false,
|
|
3042
|
+
normalize: false,
|
|
3043
|
+
lowercase: false
|
|
3044
|
+
});
|
|
3045
|
+
const stack = [];
|
|
3046
|
+
const result = [];
|
|
3047
|
+
parser.onopentag = (node) => {
|
|
3048
|
+
const lowerName = node.name.toLowerCase();
|
|
3049
|
+
const attributes = {};
|
|
3050
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) {
|
|
3051
|
+
attributes[key.toLowerCase()] = String(value);
|
|
3052
|
+
}
|
|
3053
|
+
stack.push({
|
|
3054
|
+
name: lowerName,
|
|
3055
|
+
rawName: node.name,
|
|
3056
|
+
attributes,
|
|
3057
|
+
segments: [],
|
|
3058
|
+
items: []
|
|
3059
|
+
});
|
|
3060
|
+
if (lowerName !== "resources" && lowerName !== "item" && !isResourceElementName(lowerName)) {
|
|
3061
|
+
const attrString = Object.entries(node.attributes ?? {}).map(
|
|
3062
|
+
([key, value]) => ` ${key}="${escapeAttributeValue(String(value))}"`
|
|
3063
|
+
).join("");
|
|
3064
|
+
appendSegmentToNearestResource(stack, {
|
|
3065
|
+
kind: "text",
|
|
3066
|
+
value: `<${node.name}${attrString}>`
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
parser.ontext = (text) => {
|
|
3071
|
+
if (!text) {
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
appendSegmentToNearestResource(stack, { kind: "text", value: text });
|
|
3075
|
+
};
|
|
3076
|
+
parser.oncdata = (cdata) => {
|
|
3077
|
+
appendSegmentToNearestResource(stack, { kind: "cdata", value: cdata });
|
|
3078
|
+
};
|
|
3079
|
+
parser.onclosetag = () => {
|
|
3080
|
+
const entry = stack.pop();
|
|
3081
|
+
if (!entry) {
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
const parent = stack[stack.length - 1];
|
|
3085
|
+
if (entry.name === "item" && parent) {
|
|
3086
|
+
const meta = makeTextMeta(entry.segments);
|
|
3087
|
+
parent.items.push({
|
|
3088
|
+
quantity: entry.attributes.quantity,
|
|
3089
|
+
meta
|
|
3090
|
+
});
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
if (entry.name !== "resources" && entry.name !== "item" && !isResourceElementName(entry.name)) {
|
|
3094
|
+
appendSegmentToNearestResource(stack, {
|
|
3095
|
+
kind: "text",
|
|
3096
|
+
value: `</${entry.rawName}>`
|
|
3097
|
+
});
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
if (!isResourceElementName(entry.name)) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
const name = entry.attributes.name;
|
|
3104
|
+
if (!name) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
const translatable = (entry.attributes.translatable ?? "").toLowerCase() !== "false";
|
|
3108
|
+
switch (entry.name) {
|
|
3109
|
+
case "string": {
|
|
3110
|
+
result.push({
|
|
3111
|
+
type: "string",
|
|
3112
|
+
name,
|
|
3113
|
+
translatable,
|
|
3114
|
+
meta: makeTextMeta(entry.segments)
|
|
3115
|
+
});
|
|
3116
|
+
break;
|
|
3117
|
+
}
|
|
3118
|
+
case "string-array": {
|
|
3119
|
+
result.push({
|
|
3120
|
+
type: "string-array",
|
|
3121
|
+
name,
|
|
3122
|
+
translatable,
|
|
3123
|
+
items: entry.items.map((item) => ({
|
|
3124
|
+
meta: cloneTextMeta(item.meta)
|
|
3125
|
+
}))
|
|
3126
|
+
});
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
case "plurals": {
|
|
3130
|
+
const items = [];
|
|
3131
|
+
for (const item of entry.items) {
|
|
3132
|
+
if (!item.quantity) {
|
|
3133
|
+
continue;
|
|
2317
3134
|
}
|
|
3135
|
+
items.push({
|
|
3136
|
+
quantity: item.quantity,
|
|
3137
|
+
meta: cloneTextMeta(item.meta)
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
result.push({
|
|
3141
|
+
type: "plurals",
|
|
3142
|
+
name,
|
|
3143
|
+
translatable,
|
|
3144
|
+
items
|
|
2318
3145
|
});
|
|
2319
|
-
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
3146
|
+
break;
|
|
3147
|
+
}
|
|
3148
|
+
case "bool": {
|
|
3149
|
+
result.push({
|
|
3150
|
+
type: "bool",
|
|
3151
|
+
name,
|
|
3152
|
+
translatable,
|
|
3153
|
+
meta: makeTextMeta(entry.segments)
|
|
3154
|
+
});
|
|
3155
|
+
break;
|
|
3156
|
+
}
|
|
3157
|
+
case "integer": {
|
|
3158
|
+
result.push({
|
|
3159
|
+
type: "integer",
|
|
3160
|
+
name,
|
|
3161
|
+
translatable,
|
|
3162
|
+
meta: makeTextMeta(entry.segments)
|
|
2325
3163
|
});
|
|
3164
|
+
break;
|
|
2326
3165
|
}
|
|
2327
3166
|
}
|
|
2328
|
-
}
|
|
3167
|
+
};
|
|
3168
|
+
parser.write(xml).close();
|
|
3169
|
+
return result;
|
|
3170
|
+
}
|
|
3171
|
+
function appendSegmentToNearestResource(stack, segment) {
|
|
3172
|
+
for (let index = stack.length - 1; index >= 0; index--) {
|
|
3173
|
+
const entry = stack[index];
|
|
3174
|
+
if (entry.name === "string" || entry.name === "item" || entry.name === "bool" || entry.name === "integer") {
|
|
3175
|
+
entry.segments.push(segment);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
function isResourceElementName(value) {
|
|
3181
|
+
return value === "string" || value === "string-array" || value === "plurals" || value === "bool" || value === "integer";
|
|
3182
|
+
}
|
|
3183
|
+
function deepClone(value) {
|
|
3184
|
+
return value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
3185
|
+
}
|
|
3186
|
+
function serializeElement(node) {
|
|
3187
|
+
if (!node) {
|
|
3188
|
+
return "";
|
|
3189
|
+
}
|
|
3190
|
+
const name = node["#name"] ?? "resources";
|
|
3191
|
+
if (name === "__text__") {
|
|
3192
|
+
return node._ ?? "";
|
|
3193
|
+
}
|
|
3194
|
+
if (name === "__cdata") {
|
|
3195
|
+
return `<![CDATA[${node._ ?? ""}]]>`;
|
|
3196
|
+
}
|
|
3197
|
+
if (name === "__comment__") {
|
|
3198
|
+
return `<!--${node._ ?? ""}-->`;
|
|
3199
|
+
}
|
|
3200
|
+
const attributes = node.$ ?? {};
|
|
3201
|
+
const attrString = Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttributeValue(String(value))}"`).join("");
|
|
3202
|
+
const children = Array.isArray(node.$$) ? node.$$ : [];
|
|
3203
|
+
if (children.length === 0) {
|
|
3204
|
+
const textContent = node._ ?? "";
|
|
3205
|
+
return `<${name}${attrString}>${textContent}</${name}>`;
|
|
3206
|
+
}
|
|
3207
|
+
const childContent = children.map(serializeElement).join("");
|
|
3208
|
+
return `<${name}${attrString}>${childContent}</${name}>`;
|
|
3209
|
+
}
|
|
3210
|
+
function escapeAttributeValue(value) {
|
|
3211
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'");
|
|
3212
|
+
}
|
|
3213
|
+
function decodeAndroidText(value) {
|
|
3214
|
+
return value.replace(/\\'/g, "'");
|
|
2329
3215
|
}
|
|
2330
3216
|
|
|
2331
3217
|
// src/cli/loaders/csv.ts
|
|
@@ -2751,39 +3637,320 @@ function parsePropertyLine(line) {
|
|
|
2751
3637
|
};
|
|
2752
3638
|
}
|
|
2753
3639
|
|
|
3640
|
+
// src/cli/loaders/xcode-strings/tokenizer.ts
|
|
3641
|
+
var Tokenizer = class {
|
|
3642
|
+
input;
|
|
3643
|
+
pos;
|
|
3644
|
+
line;
|
|
3645
|
+
column;
|
|
3646
|
+
constructor(input2) {
|
|
3647
|
+
this.input = input2;
|
|
3648
|
+
this.pos = 0;
|
|
3649
|
+
this.line = 1;
|
|
3650
|
+
this.column = 1;
|
|
3651
|
+
}
|
|
3652
|
+
tokenize() {
|
|
3653
|
+
const tokens = [];
|
|
3654
|
+
while (this.pos < this.input.length) {
|
|
3655
|
+
const char = this.current();
|
|
3656
|
+
if (this.isWhitespace(char)) {
|
|
3657
|
+
this.advance();
|
|
3658
|
+
continue;
|
|
3659
|
+
}
|
|
3660
|
+
if (char === "/" && this.peek() === "/") {
|
|
3661
|
+
tokens.push(this.scanSingleLineComment());
|
|
3662
|
+
continue;
|
|
3663
|
+
}
|
|
3664
|
+
if (char === "/" && this.peek() === "*") {
|
|
3665
|
+
tokens.push(this.scanMultiLineComment());
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
if (char === '"') {
|
|
3669
|
+
tokens.push(this.scanString());
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
if (char === "=") {
|
|
3673
|
+
tokens.push(this.makeToken("EQUALS" /* EQUALS */, "="));
|
|
3674
|
+
this.advance();
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
if (char === ";") {
|
|
3678
|
+
tokens.push(this.makeToken("SEMICOLON" /* SEMICOLON */, ";"));
|
|
3679
|
+
this.advance();
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
this.advance();
|
|
3683
|
+
}
|
|
3684
|
+
tokens.push(this.makeToken("EOF" /* EOF */, ""));
|
|
3685
|
+
return tokens;
|
|
3686
|
+
}
|
|
3687
|
+
scanString() {
|
|
3688
|
+
const start = this.getPosition();
|
|
3689
|
+
let value = "";
|
|
3690
|
+
this.advance();
|
|
3691
|
+
while (this.pos < this.input.length) {
|
|
3692
|
+
const char = this.current();
|
|
3693
|
+
if (char === "\\") {
|
|
3694
|
+
this.advance();
|
|
3695
|
+
if (this.pos < this.input.length) {
|
|
3696
|
+
const nextChar = this.current();
|
|
3697
|
+
value += "\\" + nextChar;
|
|
3698
|
+
this.advance();
|
|
3699
|
+
}
|
|
3700
|
+
continue;
|
|
3701
|
+
}
|
|
3702
|
+
if (char === '"') {
|
|
3703
|
+
this.advance();
|
|
3704
|
+
return {
|
|
3705
|
+
type: "STRING" /* STRING */,
|
|
3706
|
+
value,
|
|
3707
|
+
...start
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
value += char;
|
|
3711
|
+
this.advance();
|
|
3712
|
+
}
|
|
3713
|
+
return {
|
|
3714
|
+
type: "STRING" /* STRING */,
|
|
3715
|
+
value,
|
|
3716
|
+
...start
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
scanSingleLineComment() {
|
|
3720
|
+
const start = this.getPosition();
|
|
3721
|
+
let value = "";
|
|
3722
|
+
this.advance();
|
|
3723
|
+
this.advance();
|
|
3724
|
+
while (this.pos < this.input.length && this.current() !== "\n") {
|
|
3725
|
+
value += this.current();
|
|
3726
|
+
this.advance();
|
|
3727
|
+
}
|
|
3728
|
+
return {
|
|
3729
|
+
type: "COMMENT_SINGLE" /* COMMENT_SINGLE */,
|
|
3730
|
+
value,
|
|
3731
|
+
...start
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
scanMultiLineComment() {
|
|
3735
|
+
const start = this.getPosition();
|
|
3736
|
+
let value = "";
|
|
3737
|
+
this.advance();
|
|
3738
|
+
this.advance();
|
|
3739
|
+
while (this.pos < this.input.length) {
|
|
3740
|
+
if (this.current() === "*" && this.peek() === "/") {
|
|
3741
|
+
this.advance();
|
|
3742
|
+
this.advance();
|
|
3743
|
+
return {
|
|
3744
|
+
type: "COMMENT_MULTI" /* COMMENT_MULTI */,
|
|
3745
|
+
value,
|
|
3746
|
+
...start
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
value += this.current();
|
|
3750
|
+
this.advance();
|
|
3751
|
+
}
|
|
3752
|
+
return {
|
|
3753
|
+
type: "COMMENT_MULTI" /* COMMENT_MULTI */,
|
|
3754
|
+
value,
|
|
3755
|
+
...start
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
current() {
|
|
3759
|
+
return this.input[this.pos];
|
|
3760
|
+
}
|
|
3761
|
+
peek() {
|
|
3762
|
+
if (this.pos + 1 < this.input.length) {
|
|
3763
|
+
return this.input[this.pos + 1];
|
|
3764
|
+
}
|
|
3765
|
+
return null;
|
|
3766
|
+
}
|
|
3767
|
+
advance() {
|
|
3768
|
+
if (this.pos < this.input.length) {
|
|
3769
|
+
if (this.current() === "\n") {
|
|
3770
|
+
this.line++;
|
|
3771
|
+
this.column = 1;
|
|
3772
|
+
} else {
|
|
3773
|
+
this.column++;
|
|
3774
|
+
}
|
|
3775
|
+
this.pos++;
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
isWhitespace(char) {
|
|
3779
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
3780
|
+
}
|
|
3781
|
+
getPosition() {
|
|
3782
|
+
return {
|
|
3783
|
+
line: this.line,
|
|
3784
|
+
column: this.column
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
makeToken(type, value) {
|
|
3788
|
+
return {
|
|
3789
|
+
type,
|
|
3790
|
+
value,
|
|
3791
|
+
...this.getPosition()
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
|
|
3796
|
+
// src/cli/loaders/xcode-strings/escape.ts
|
|
3797
|
+
function unescapeString(raw) {
|
|
3798
|
+
let result = "";
|
|
3799
|
+
let i = 0;
|
|
3800
|
+
while (i < raw.length) {
|
|
3801
|
+
if (raw[i] === "\\" && i + 1 < raw.length) {
|
|
3802
|
+
const nextChar = raw[i + 1];
|
|
3803
|
+
switch (nextChar) {
|
|
3804
|
+
case '"':
|
|
3805
|
+
result += '"';
|
|
3806
|
+
i += 2;
|
|
3807
|
+
break;
|
|
3808
|
+
case "\\":
|
|
3809
|
+
result += "\\";
|
|
3810
|
+
i += 2;
|
|
3811
|
+
break;
|
|
3812
|
+
case "n":
|
|
3813
|
+
result += "\n";
|
|
3814
|
+
i += 2;
|
|
3815
|
+
break;
|
|
3816
|
+
case "t":
|
|
3817
|
+
result += " ";
|
|
3818
|
+
i += 2;
|
|
3819
|
+
break;
|
|
3820
|
+
case "r":
|
|
3821
|
+
result += "\r";
|
|
3822
|
+
i += 2;
|
|
3823
|
+
break;
|
|
3824
|
+
default:
|
|
3825
|
+
result += raw[i];
|
|
3826
|
+
i++;
|
|
3827
|
+
break;
|
|
3828
|
+
}
|
|
3829
|
+
} else {
|
|
3830
|
+
result += raw[i];
|
|
3831
|
+
i++;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
return result;
|
|
3835
|
+
}
|
|
3836
|
+
function escapeString(str) {
|
|
3837
|
+
if (str == null) {
|
|
3838
|
+
return "";
|
|
3839
|
+
}
|
|
3840
|
+
let result = "";
|
|
3841
|
+
for (let i = 0; i < str.length; i++) {
|
|
3842
|
+
const char = str[i];
|
|
3843
|
+
switch (char) {
|
|
3844
|
+
case "\\":
|
|
3845
|
+
result += "\\\\";
|
|
3846
|
+
break;
|
|
3847
|
+
case '"':
|
|
3848
|
+
result += '\\"';
|
|
3849
|
+
break;
|
|
3850
|
+
case "\n":
|
|
3851
|
+
result += "\\n";
|
|
3852
|
+
break;
|
|
3853
|
+
case "\r":
|
|
3854
|
+
result += "\\r";
|
|
3855
|
+
break;
|
|
3856
|
+
case " ":
|
|
3857
|
+
result += "\\t";
|
|
3858
|
+
break;
|
|
3859
|
+
default:
|
|
3860
|
+
result += char;
|
|
3861
|
+
break;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
return result;
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
// src/cli/loaders/xcode-strings/parser.ts
|
|
3868
|
+
var Parser = class {
|
|
3869
|
+
tokens;
|
|
3870
|
+
pos;
|
|
3871
|
+
constructor(tokens) {
|
|
3872
|
+
this.tokens = tokens;
|
|
3873
|
+
this.pos = 0;
|
|
3874
|
+
}
|
|
3875
|
+
parse() {
|
|
3876
|
+
const result = {};
|
|
3877
|
+
while (this.pos < this.tokens.length) {
|
|
3878
|
+
const token = this.current();
|
|
3879
|
+
if (token.type === "COMMENT_SINGLE" /* COMMENT_SINGLE */ || token.type === "COMMENT_MULTI" /* COMMENT_MULTI */) {
|
|
3880
|
+
this.advance();
|
|
3881
|
+
continue;
|
|
3882
|
+
}
|
|
3883
|
+
if (token.type === "EOF" /* EOF */) {
|
|
3884
|
+
break;
|
|
3885
|
+
}
|
|
3886
|
+
if (token.type === "STRING" /* STRING */) {
|
|
3887
|
+
const entry = this.parseEntry();
|
|
3888
|
+
if (entry) {
|
|
3889
|
+
result[entry.key] = entry.value;
|
|
3890
|
+
}
|
|
3891
|
+
continue;
|
|
3892
|
+
}
|
|
3893
|
+
this.advance();
|
|
3894
|
+
}
|
|
3895
|
+
return result;
|
|
3896
|
+
}
|
|
3897
|
+
parseEntry() {
|
|
3898
|
+
const keyToken = this.current();
|
|
3899
|
+
if (keyToken.type !== "STRING" /* STRING */) {
|
|
3900
|
+
return null;
|
|
3901
|
+
}
|
|
3902
|
+
const key = keyToken.value;
|
|
3903
|
+
this.advance();
|
|
3904
|
+
if (!this.expect("EQUALS" /* EQUALS */)) {
|
|
3905
|
+
return null;
|
|
3906
|
+
}
|
|
3907
|
+
const valueToken = this.current();
|
|
3908
|
+
if (valueToken.type !== "STRING" /* STRING */) {
|
|
3909
|
+
return null;
|
|
3910
|
+
}
|
|
3911
|
+
const rawValue = valueToken.value;
|
|
3912
|
+
this.advance();
|
|
3913
|
+
if (!this.expect("SEMICOLON" /* SEMICOLON */)) {
|
|
3914
|
+
}
|
|
3915
|
+
const value = unescapeString(rawValue);
|
|
3916
|
+
return { key, value };
|
|
3917
|
+
}
|
|
3918
|
+
current() {
|
|
3919
|
+
return this.tokens[this.pos];
|
|
3920
|
+
}
|
|
3921
|
+
advance() {
|
|
3922
|
+
if (this.pos < this.tokens.length) {
|
|
3923
|
+
this.pos++;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
expect(type) {
|
|
3927
|
+
if (this.current()?.type === type) {
|
|
3928
|
+
this.advance();
|
|
3929
|
+
return true;
|
|
3930
|
+
}
|
|
3931
|
+
return false;
|
|
3932
|
+
}
|
|
3933
|
+
};
|
|
3934
|
+
|
|
2754
3935
|
// src/cli/loaders/xcode-strings.ts
|
|
2755
3936
|
function createXcodeStringsLoader() {
|
|
2756
3937
|
return createLoader({
|
|
2757
3938
|
async pull(locale, input2) {
|
|
2758
|
-
const
|
|
2759
|
-
const
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
if (trimmedLine && !trimmedLine.startsWith("//")) {
|
|
2763
|
-
const match2 = trimmedLine.match(/^"(.+)"\s*=\s*"(.+)";$/);
|
|
2764
|
-
if (match2) {
|
|
2765
|
-
const [, key, value] = match2;
|
|
2766
|
-
result[key] = unescapeXcodeString(value);
|
|
2767
|
-
}
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
3939
|
+
const tokenizer = new Tokenizer(input2);
|
|
3940
|
+
const tokens = tokenizer.tokenize();
|
|
3941
|
+
const parser = new Parser(tokens);
|
|
3942
|
+
const result = parser.parse();
|
|
2770
3943
|
return result;
|
|
2771
3944
|
},
|
|
2772
3945
|
async push(locale, payload) {
|
|
2773
|
-
const lines = Object.entries(payload).map(([key, value]) => {
|
|
2774
|
-
const escapedValue =
|
|
3946
|
+
const lines = Object.entries(payload).filter(([_36, value]) => value != null).map(([key, value]) => {
|
|
3947
|
+
const escapedValue = escapeString(value);
|
|
2775
3948
|
return `"${key}" = "${escapedValue}";`;
|
|
2776
3949
|
});
|
|
2777
3950
|
return lines.join("\n");
|
|
2778
3951
|
}
|
|
2779
3952
|
});
|
|
2780
3953
|
}
|
|
2781
|
-
function unescapeXcodeString(str) {
|
|
2782
|
-
return str.replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
|
|
2783
|
-
}
|
|
2784
|
-
function escapeXcodeString(str) {
|
|
2785
|
-
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2786
|
-
}
|
|
2787
3954
|
|
|
2788
3955
|
// src/cli/loaders/xcode-stringsdict.ts
|
|
2789
3956
|
import plist from "plist";
|
|
@@ -4095,7 +5262,7 @@ function pushNewFile(locale, translations, originalLocale) {
|
|
|
4095
5262
|
}
|
|
4096
5263
|
|
|
4097
5264
|
// src/cli/loaders/xml.ts
|
|
4098
|
-
import { parseStringPromise as parseStringPromise2, Builder
|
|
5265
|
+
import { parseStringPromise as parseStringPromise2, Builder } from "xml2js";
|
|
4099
5266
|
function normalizeXMLString(xmlString) {
|
|
4100
5267
|
return xmlString.replace(/\s+/g, " ").replace(/>\s+</g, "><").replace("\n", "").trim();
|
|
4101
5268
|
}
|
|
@@ -4122,7 +5289,7 @@ function createXmlLoader() {
|
|
|
4122
5289
|
},
|
|
4123
5290
|
async push(locale, data) {
|
|
4124
5291
|
try {
|
|
4125
|
-
const builder = new
|
|
5292
|
+
const builder = new Builder({ headless: true });
|
|
4126
5293
|
const xmlOutput = builder.buildObject(data);
|
|
4127
5294
|
const expectedOutput = normalizeXMLString(xmlOutput);
|
|
4128
5295
|
return expectedOutput;
|
|
@@ -12212,7 +13379,7 @@ async function renderHero2() {
|
|
|
12212
13379
|
// package.json
|
|
12213
13380
|
var package_default = {
|
|
12214
13381
|
name: "lingo.dev",
|
|
12215
|
-
version: "0.113.
|
|
13382
|
+
version: "0.113.7",
|
|
12216
13383
|
description: "Lingo.dev CLI",
|
|
12217
13384
|
private: false,
|
|
12218
13385
|
publishConfig: {
|
|
@@ -12419,6 +13586,7 @@ var package_default = {
|
|
|
12419
13586
|
"remark-parse": "^11.0.0",
|
|
12420
13587
|
"remark-rehype": "^11.1.2",
|
|
12421
13588
|
"remark-stringify": "^11.0.0",
|
|
13589
|
+
sax: "^1.4.1",
|
|
12422
13590
|
"srt-parser-2": "^1.2.3",
|
|
12423
13591
|
unified: "^11.0.5",
|
|
12424
13592
|
"unist-util-visit": "^5.0.0",
|