Skip to content

Commit 290455e

Browse files
feat: add multilingual support for OBF and Asterics grid files
- Update `BaseProcessor.processTexts` to accept `targetLocale` for adding languages - Implement multilingual writing support in `ObfProcessor` (updating `strings` dictionary) - Implement multilingual writing support in `AstericsGridProcessor` (updating `label` maps) - Ensure `ObfProcessor` correctly resolves strings based on locale during load - Add comprehensive tests for multilingual workflows Co-authored-by: willwade <229352+willwade@users.noreply.github.com>
1 parent e369553 commit 290455e

14 files changed

Lines changed: 496 additions & 31 deletions

src/core/baseProcessor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,13 @@ abstract class BaseProcessor {
132132
abstract loadIntoTree(filePathOrBuffer: ProcessorInput): Promise<AACTree>;
133133

134134
// Process texts (e.g., apply translations) and return new file/buffer
135+
// If targetLocale is provided, it attempts to add the language to the file (multilingual support)
136+
// Otherwise, it replaces the existing text (translation/localization)
135137
abstract processTexts(
136138
filePathOrBuffer: ProcessorInput,
137139
translations: Map<string, string>,
138-
outputPath: string
140+
outputPath: string,
141+
targetLocale?: string
139142
): Promise<BinaryOutput>;
140143

141144
// Save tree structure back to file/buffer

src/processors/applePanelsProcessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ class ApplePanelsProcessor extends BaseProcessor {
419419
async processTexts(
420420
filePathOrBuffer: ProcessorInput,
421421
translations: Map<string, string>,
422-
outputPath: string
422+
outputPath: string,
423+
targetLocale?: string
423424
): Promise<Uint8Array> {
424425
// Load the tree, apply translations, and save to new file
425426
const tree = await this.loadIntoTree(filePathOrBuffer);

src/processors/astericsGridProcessor.ts

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,8 @@ class AstericsGridProcessor extends BaseProcessor {
13101310
async processTexts(
13111311
filePathOrBuffer: ProcessorInput,
13121312
translations: Map<string, string>,
1313-
outputPath: string
1313+
outputPath: string,
1314+
targetLocale?: string
13141315
): Promise<Uint8Array> {
13151316
await Promise.resolve();
13161317
let content = readTextFromInput(filePathOrBuffer);
@@ -1323,7 +1324,7 @@ class AstericsGridProcessor extends BaseProcessor {
13231324
const grdFile: AstericsGridFile = JSON.parse(content);
13241325

13251326
// Apply translations directly to the JSON structure for comprehensive coverage
1326-
this.applyTranslationsToGridFile(grdFile, translations);
1327+
this.applyTranslationsToGridFile(grdFile, translations, targetLocale);
13271328

13281329
// Write the translated file
13291330
writeTextToPath(outputPath, JSON.stringify(grdFile, null, 2));
@@ -1332,39 +1333,86 @@ class AstericsGridProcessor extends BaseProcessor {
13321333

13331334
private applyTranslationsToGridFile(
13341335
grdFile: AstericsGridFile,
1335-
translations: Map<string, string>
1336+
translations: Map<string, string>,
1337+
targetLocale?: string
13361338
): void {
13371339
grdFile.grids.forEach((grid: GridData) => {
13381340
// Translate grid labels
13391341
if (grid.label) {
1340-
Object.keys(grid.label).forEach((lang) => {
1341-
const originalText = grid.label[lang];
1342-
if (originalText && translations.has(originalText)) {
1342+
if (typeof grid.label === 'string') {
1343+
const originalText = grid.label as string;
1344+
if (translations.has(originalText)) {
13431345
const translation = translations.get(originalText);
13441346
if (translation !== undefined) {
1345-
grid.label[lang] = translation;
1347+
if (targetLocale) {
1348+
// Upgrade to object format
1349+
grid.label = {
1350+
en: originalText, // Assume 'en' for legacy string labels
1351+
[targetLocale]: translation,
1352+
};
1353+
} else {
1354+
grid.label = translation as any;
1355+
}
13461356
}
13471357
}
1348-
});
1358+
} else {
1359+
Object.keys(grid.label).forEach((lang) => {
1360+
const originalText = grid.label[lang];
1361+
if (originalText && translations.has(originalText)) {
1362+
const translation = translations.get(originalText);
1363+
if (translation !== undefined) {
1364+
if (targetLocale) {
1365+
grid.label[targetLocale] = translation;
1366+
} else {
1367+
grid.label[lang] = translation;
1368+
}
1369+
}
1370+
}
1371+
});
1372+
}
13491373
}
13501374

13511375
// Translate grid elements
13521376
grid.gridElements.forEach((element: GridElement) => {
13531377
// Translate element labels
13541378
if (element.label) {
1355-
Object.keys(element.label).forEach((lang) => {
1356-
const originalText = element.label[lang];
1357-
if (originalText && translations.has(originalText)) {
1379+
if (typeof element.label === 'string') {
1380+
const originalText = element.label as string;
1381+
if (translations.has(originalText)) {
13581382
const translation = translations.get(originalText);
13591383
if (translation !== undefined) {
1360-
element.label[lang] = translation;
1384+
if (targetLocale) {
1385+
// Upgrade to object format
1386+
element.label = {
1387+
en: originalText, // Assume 'en' for legacy string labels
1388+
[targetLocale]: translation,
1389+
};
1390+
} else {
1391+
element.label = translation as any;
1392+
}
13611393
}
13621394
}
1363-
});
1395+
} else {
1396+
Object.keys(element.label).forEach((lang) => {
1397+
const originalText = element.label[lang];
1398+
if (originalText && translations.has(originalText)) {
1399+
const translation = translations.get(originalText);
1400+
if (translation !== undefined) {
1401+
if (targetLocale) {
1402+
element.label[targetLocale] = translation;
1403+
} else {
1404+
element.label[lang] = translation;
1405+
}
1406+
}
1407+
}
1408+
});
1409+
}
13641410
}
13651411

13661412
// Translate word forms
1367-
if (element.wordForms) {
1413+
// Word forms are typically specific to a language, so adding a target locale might require structure change
1414+
// For now, we only translate in place if no targetLocale, or skip if targetLocale is set (as word forms are language specific)
1415+
if (element.wordForms && !targetLocale) {
13681416
element.wordForms.forEach((wordForm: WordForm) => {
13691417
if (wordForm.value && translations.has(wordForm.value)) {
13701418
const translation = translations.get(wordForm.value);
@@ -1377,13 +1425,17 @@ class AstericsGridProcessor extends BaseProcessor {
13771425

13781426
// Translate action-specific texts
13791427
element.actions.forEach((action: GridAction) => {
1380-
this.applyTranslationsToAction(action, translations);
1428+
this.applyTranslationsToAction(action, translations, targetLocale);
13811429
});
13821430
});
13831431
});
13841432
}
13851433

1386-
private applyTranslationsToAction(action: GridAction, translations: Map<string, string>): void {
1434+
private applyTranslationsToAction(
1435+
action: GridAction,
1436+
translations: Map<string, string>,
1437+
targetLocale?: string
1438+
): void {
13871439
switch (action.modelName) {
13881440
case 'GridActionSpeakCustom':
13891441
if (action.speakText && typeof action.speakText === 'object') {
@@ -1393,7 +1445,11 @@ class AstericsGridProcessor extends BaseProcessor {
13931445
if (typeof originalText === 'string' && translations.has(originalText)) {
13941446
const translation = translations.get(originalText);
13951447
if (translation !== undefined) {
1396-
speakTextMap[lang] = translation;
1448+
if (targetLocale) {
1449+
speakTextMap[targetLocale] = translation;
1450+
} else {
1451+
speakTextMap[lang] = translation;
1452+
}
13971453
}
13981454
}
13991455
});

src/processors/dotProcessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ class DotProcessor extends BaseProcessor {
217217
async processTexts(
218218
filePathOrBuffer: ProcessorInput,
219219
translations: Map<string, string>,
220-
outputPath: string
220+
outputPath: string,
221+
targetLocale?: string
221222
): Promise<Uint8Array> {
222223
await Promise.resolve();
223224
const content = readTextFromInput(filePathOrBuffer);

src/processors/excelProcessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export class ExcelProcessor extends BaseProcessor {
5353
async processTexts(
5454
_filePathOrBuffer: ProcessorInput,
5555
_translations: Map<string, string>,
56-
outputPath: string
56+
outputPath: string,
57+
targetLocale?: string
5758
): Promise<Uint8Array> {
5859
await Promise.resolve();
5960
console.warn('ExcelProcessor.processTexts is not implemented yet.');

src/processors/gridsetProcessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,8 @@ class GridsetProcessor extends BaseProcessor {
16031603
async processTexts(
16041604
filePathOrBuffer: ProcessorInput,
16051605
translations: Map<string, string>,
1606-
outputPath: string
1606+
outputPath: string,
1607+
targetLocale?: string
16071608
): Promise<Uint8Array> {
16081609
// Load the tree, apply translations, and save to new file
16091610
const tree = await this.loadIntoTree(filePathOrBuffer);

0 commit comments

Comments
 (0)