Skip to content

Commit

Permalink
Add support for xcstrings format
Browse files Browse the repository at this point in the history
  • Loading branch information
icodesign committed Sep 2, 2024
1 parent 6ab8c43 commit 4505ca5
Show file tree
Hide file tree
Showing 17 changed files with 608 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/base/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum LocalizationFormat {
XCODE = 'xcode', // Xcode project
XCLOC = 'xcloc', // Xcloc file
STRINGS = 'strings', // Apple strings files (.strings)
XCSTRINGS = 'xcstrings', // Apple strings catalog
XLIFF = 'xliff', // XLIFF file
JSON = 'json', // JSON file
}
Expand Down
10 changes: 10 additions & 0 deletions packages/base/src/spinner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Spinner {
update(message?: string, options?: { logging: boolean }): Spinner;
fail(message?: string, options?: { logging: boolean }): Spinner;
succeed(message?: string, options?: { logging: boolean }): Spinner;
stop(options?: { persist?: boolean }): Spinner;
}

class OraSpinner {
Expand Down Expand Up @@ -56,6 +57,15 @@ class OraSpinner {
}
return this;
}

stop(options?: { persist?: boolean }) {
if (options?.persist !== undefined && options.persist) {
this.ora.stopAndPersist();
} else {
this.ora.stop();
}
return this;
}
}

export default new OraSpinner();
235 changes: 235 additions & 0 deletions packages/ioloc/src/export/parser/xcstrings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { logger } from '@repo/base/logger';
import fs from 'node:fs';
import path from 'node:path';

import { textHash } from '../../utils.js';
import { encodeXliffAttributeValue } from '../../xliff/index.js';
import { Unit, Xliff } from '../../xliff/xliff-spec.js';
import { ExportParser } from '../index.js';

export interface XCStringsFile {
sourceLanguage: string;
strings: {
[key: string]: {
extractionState?: string;
comment?: string;
localizations?: {
[lang: string]: {
stringUnit?: {
state?: string;
value?: string;
};
stringSet?: {
state?: string;
values?: string[];
};
};
};
};
};
version: string;
}

export class XCStringsParser implements ExportParser {
async parse(
filePath: string,
language: string,
sourceFilePath: string,
sourceLanguage: string,
basePath: string,
): Promise<Xliff> {
const fileId = textHash(sourceFilePath);
const xliffOriginalPath = path.relative(basePath, filePath);

const fileContent = await fs.promises.readFile(filePath, 'utf-8');
const xcstrings: XCStringsFile = parseXCStrings(fileContent);

const targetElements: Unit[] = [];

for (const [key, value] of Object.entries(xcstrings.strings)) {
const unitElements: Unit['elements'] = [];
if (!value.localizations) {
unitElements.push({
name: 'segment',
type: 'element',
attributes: {
state: 'initial',
},
elements: [
{
name: 'source',
type: 'element',
elements: [
{
type: 'text',
text: key,
},
],
},
],
});
} else {
const sourceText =
value.localizations[sourceLanguage]?.stringUnit?.value ||
value.localizations[sourceLanguage]?.stringSet?.values?.[0] ||
key;
const targetText =
(value.localizations &&
value.localizations[language]?.stringUnit?.value) ||
(value.localizations &&
value.localizations[language]?.stringSet?.values?.[0]) ||
'';
const stringState =
value.localizations[language]?.stringUnit?.state ||
value.localizations[language]?.stringSet?.state;
const state = stringState === 'translated' ? 'translated' : 'initial';

if (value.comment) {
unitElements.push({
name: 'notes',
type: 'element',
elements: [
{
name: 'note',
type: 'element',
elements: [
{
type: 'text',
text: value.comment,
},
],
},
],
});
}

unitElements.push({
name: 'segment',
type: 'element',
attributes: {
state,
},
elements: [
{
name: 'source',
type: 'element',
elements: [
{
type: 'text',
text: sourceText,
},
],
},
{
name: 'target',
type: 'element',
elements: [
{
type: 'text',
text: targetText,
},
],
},
],
});
}

targetElements.push({
name: 'unit',
type: 'element',
attributes: {
id: encodeXliffAttributeValue(key),
extractionState: value.extractionState,
},
elements: unitElements,
});
}

return {
name: 'xliff',
type: 'element',
attributes: {
version: '2.0',
srcLang: sourceLanguage,
trgLang: language,
},
elements: [
{
name: 'file',
type: 'element',
attributes: {
id: fileId,
original: xliffOriginalPath,
},
elements: targetElements,
},
],
};
}
}

export function parseXCStrings(content: string): XCStringsFile {
try {
const parsed: XCStringsFile = JSON.parse(content);
// Add validation logic here if needed
return parsed;
} catch (error) {
throw new Error(`Failed to parse XCStrings file: ${error}`);
}
}

// export function extractTranslations(
// xcstrings: XCStringsFile,
// ): Record<string, Record<string, string>> {
// const translations: Record<string, Record<string, string>> = {};

// for (const [key, value] of Object.entries(xcstrings.strings)) {
// for (const [lang, localization] of Object.entries(value.localizations)) {
// if (!translations[lang]) {
// translations[lang] = {};
// }
// translations[lang][key] = localization.stringUnit?.values[0] || '';
// }
// }

// return translations;
// }

// export function createXCStringsFile(
// translations: Record<string, Record<string, string>>,
// sourceLanguage: string,
// ): XCStringsFile {
// const xcstrings: XCStringsFile = {
// sourceLanguage,
// strings: {},
// version: '1.0',
// };

// for (const [key, langValues] of Object.entries(
// translations[sourceLanguage],
// )) {
// xcstrings.strings[key] = {
// extractionState: 'manual',
// localizations: {},
// };

// for (const lang of Object.keys(translations)) {
// xcstrings.strings[key].localizations[lang] = {
// stringUnit: {
// state: lang === sourceLanguage ? 'translated' : 'new',
// values: [translations[lang][key] || ''],
// },
// };
// }
// }

// return xcstrings;
// }

// export function writeXCStringsFile(
// filePath: string,
// xcstrings: XCStringsFile,
// ): Promise<void> {
// const content = JSON.stringify(xcstrings, null, 2);
// return fs.promises.writeFile(filePath, content, 'utf-8');
// }
Loading

0 comments on commit 4505ca5

Please sign in to comment.