123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- var fs = require('fs');
- function parseAndroid(data) {
- const rgxKeyValue = /<string name="(.*)">(.*)<\/string>/;
- const rgxCommentBlock = /<!-- ?(.*?) ?-->/;
- const rgxCommentStart = /<!-- ?(.*)/;
- const rgxCommentEnd = /(.*?) ?-->/;
- const rgxPluralsStart = /<plurals name="(.*)">/;
- const rgxPluralsEnd = /\s<\/plurals>/
- let lines = data.trim().split('\n');
- let result = {
- parsed: [],
- parsedPlurals: new Map()
- };
- let multilineComment = false;
- let pluralsDefinitionKey = null;
- for (let line of lines) {
- let kv = line.match(rgxKeyValue);
- if (kv != null) {
- value = kv[2].
- replace(/([^\\])(")/g, '$1\\$2').
- replace(/"/g, '\\"').
- replace(/</g, '<').
- replace(/>/g, '>').
- replace(/&/g, '&').
- replace(/\$s/ig, '$@').
- replace(/\%s/ig, '%1$@')
- let countOfPlaceholders = (value.match(/\%1\$\@/g) || []).length
- if (countOfPlaceholders > 1) {
- console.error("\n\n\n ERROR: Placeholder mismatch. A source file contained '%s' and '%1$s' in the same resource which we are not willing to fix automatically. Please fix the input source on tranisfex first! context:\n" + line + "\n\n\n")
- continue;
- }
- result.parsed.push([kv[1], value])
- continue;
- }
- let blockComment = line.match(rgxCommentBlock);
- if (blockComment) {
- result.parsed.push(blockComment[1]);
- continue;
- }
- let commentStart = line.match(rgxCommentStart);
- if (commentStart && !pluralsDefinition) {
- result.parsed.push(commentStart[1]);
- multilineComment = true;
- continue;
- }
-
- if (multilineComment) {
- let commentEnd = line.match(rgxCommentEnd);
- if (commentEnd) {
- result.parsed[result.parsed.length - 1] += '\n' + commentEnd[1];
- multilineComment = false;
- } else {
- result.parsed[result.parsed.length - 1] += '\n' + line;
- }
- continue;
- }
- let pluralsStart = line.match(rgxPluralsStart);
- if (pluralsStart) {
- pluralsDefinitionKey = pluralsStart[1];
- result.parsedPlurals.set(pluralsDefinitionKey, [ ]);
- continue;
- }
- if (pluralsDefinitionKey) {
- let pluralsEnd = line.match(rgxPluralsEnd)
- if (pluralsEnd) {
- pluralsDefinitionKey = null
- continue;
- } else if (isEmpty(line)) {
- continue;
- } else {
- result.parsedPlurals.get(pluralsDefinitionKey).push(line);
- }
- }
-
- if (isEmpty(line))
- result.parsed.push('');
- }
-
- return result;
- }
- function isEmpty(line) {
- return /^\s*$/.test(line);
- }
- function toStringsDict(pluralsMap) {
- if (!pluralsMap || pluralsMap.length == 0) {
- return;
- }
- const rgxZero = /<item quantity="zero">(.*)<\/item>/;
- const rgxOne = /<item quantity="one">(.*)<\/item>/;
- const rgxTwo = /<item quantity="two">(.*)<\/item>/;
- const rgxFew = /<item quantity="few">(.*)<\/item>/;
- const rgxMany = /<item quantity="many">(.*)<\/item>/;
- const rgxOther = /<item quantity="other">(.*)<\/item>/;
- let out = '\<?xml version=\"1.0\" encoding=\"UTF-8\"?\>\n';
- out += '\<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"\>\n';
- out += '\<plist version="1.0"\>\n';
- out += '\<dict\>\n';
- for (keyValuePair of pluralsMap) {
- let key = keyValuePair[0];
- out += '\t\<key\>' + key + '\</key\>\n';
- out += '\t\<dict\>\n';
- out += '\t\t\<key\>NSStringLocalizedFormatKey\</key\>\n'
- out += '\t\t\<string\>%#@localized_format_key@\</string\>\n'
- out += '\t\t\<key\>localized_format_key\</key\>\n'
- out += '\t\t\<dict\>\n'
- out += '\t\t\t\<key\>NSStringFormatSpecTypeKey\</key\>\n'
- out += '\t\t\t\<string\>NSStringPluralRuleType\</string\>\n'
- out += '\t\t\t\<key\>NSStringFormatValueTypeKey\</key\>\n'
- out += '\t\t\t\<string\>d\</string\>\n'
- let lines = keyValuePair[1];
- let zero = lines.filter( value => value.match(rgxZero));
- let one = lines.filter( value => value.match(rgxOne));
- let two = lines.filter( value => value.match(rgxTwo));
- let few = lines.filter( value => value.match(rgxFew));
- let many = lines.filter( value => value.match(rgxMany));
- let other = lines.filter( value => value.match(rgxOther))
- if (zero.length > 0) {
- out += '\t\t\t\<key\>zero\</key\>\n';
- out += '\t\t\t\<string\>'+zero[0].match(rgxZero)[1]+'\</string\>\n';
- }
- if (one.length > 0) {
- out += '\t\t\t\<key\>one\</key\>\n';
- out += '\t\t\t\<string\>'+one[0].match(rgxOne)[1]+'\</string\>\n';
- }
- if (two.length > 0) {
- out += '\t\t\t\<key\>two\</key\>\n';
- out += '\t\t\t\<string\>'+two[0].match(rgxTwo)[1]+'\</string\>\n';
- }
- if (few.length > 0) {
- out += '\t\t\t\<key\>few\</key\>\n';
- out += '\t\t\t\<string\>'+few[0].match(rgxFew)[1]+'\</string\>\n';
- }
- if (many.length > 0) {
- out += '\t\t\t\<key\>many\</key\>\n';
- out += '\t\t\t\<string\>'+many[0].match(rgxMany)[1]+'\</string\>\n';
- }
- if (other.length > 0) {
- out += '\t\t\t\<key\>other\</key\>\n';
- out += '\t\t\t\<string\>'+other[0].match(rgxOther)[1]+'\</string\>\n';
- }
- out += '\t\t\</dict\>\n'
- out += '\t\</dict\>\n';
- }
- out += '\</dict\>\n';
- out += '\</plist\>\n';
- return out;
- }
- function toInfoPlistStrings(lines) {
- let out = '';
- for (let line of lines) {
- if (typeof line === 'string') {
- continue;
- } else {
- let key = line[0];
- if (!key.startsWith("InfoPlist_")) {
- continue;
- }
- key = key.replace('InfoPlist_', '');
- out += `${key} = "${line[1]}";\n`;
- }
- }
- return out;
- }
- function toLocalizableStrings(lines) {
- let out = '';
- for (let line of lines) {
- if (typeof line === 'string') {
- if (line === '') {
- out += '\n';
- continue;
- }
-
- if (/\n/.test(line))
- out += '/* ' + line + ' */';
- else
- out += '// ' + line;
- } else {
- let key = line[0];
- if (key.startsWith("InfoPlist_")) {
- continue;
- }
- out += `"${key}" = "${line[1]}";`;
- }
- out += '\n';
- }
- return out;
- }
- function merge(base, addendum){
- var out = [].concat(base).filter(value => {
- return value != null;
- });
- for(let i in addendum){
- add = true;
- for (let j in base) {
- if (base[j][0] != undefined &&
- addendum[i][0] != undefined &&
- base[j][0] === addendum[i][0]) {
- add = false;
- break;
- }
- }
- if (add) {
- out.push(addendum[i]);
- }
- }
- return out;
- }
- function mergePlurals(base, appendum) {
- for (keyValuePair of appendum) {
- let key = keyValuePair[0];
- if (base[key] === undefined) {
- base.set(key, keyValuePair[1]);
- }
- }
- return base;
- }
- function parseXMLAndAppend(allElements, stringsXML) {
- var text = fs.readFileSync(stringsXML, 'utf-8').toString();
- let result = parseAndroid(text)
- allElements.parsed = merge(allElements.parsed, result.parsed);
- allElements.parsedPlurals = mergePlurals(allElements.parsedPlurals, result.parsedPlurals);
- return allElements;
- }
- function convertAndroidToIOS(stringsXMLArray, appleStrings) {
- let allElements = {
- parsed: [],
- parsedPlurals: new Map()
- };
- for (entry of stringsXMLArray) {
- allElements = parseXMLAndAppend(allElements, entry)
- console.log("parsed " + allElements.parsed.length + " entries of " + entry + " for Localizable.strings and " + allElements.parsedPlurals.size + " entries for Localizable.stringsdict");
- }
- let iosFormatted = toLocalizableStrings(allElements.parsed);
- let iosFormattedInfoPlist = toInfoPlistStrings(allElements.parsed);
- let iosFormattedPlurals = toStringsDict(allElements.parsedPlurals);
- let localizableStrings = output + "/Localizable.strings";
- let infoPlistStrings = output + "/InfoPlist.strings";
- let stringsDict = output + "/Localizable.stringsdict";
- fs.writeFile(localizableStrings, iosFormatted, function (err) {
- if (err) {
- console.error("Error converting " + stringsXMLArray + " to " + localizableStrings);
- throw err;
- }
- });
- fs.writeFile(infoPlistStrings, iosFormattedInfoPlist, function (err) {
- if (err) {
- console.error("Error converting " + stringsXMLArray + " to " + infoPlistStrings);
- throw err;
- }
- });
- fs.writeFile(stringsDict, iosFormattedPlurals, function (err) {
- if (err) {
- console.error("Error converting " + stringsXMLArray + " to " + stringsDict);
- throw err;
- }
- });
- }
- if (process.argv.length < 4) {
- console.error('Too less arguments provided. \nExample:\n ' +
- "node convertTranslations.js stringsInputfile1.xml stringsInputfile2.xml stringsInputfileN.xml path/to/outputfolder");
- process.exit(1);
- }
- var input = process.argv.slice(2, process.argv.length - 1)
- var output = process.argv[process.argv.length - 1];
- convertAndroidToIOS(input, output)
|