집단 지성을 이용한 안드로이드 다국어 지원 방법 #0에 이어서 안드로이드의 언어 파일에서 엑셀 파일을 생성하는 툴을 제작했다. 이 툴은 처음 한 번만 사용되지만 이게 없다면 개별 언어파일에서 해당 스트링 리소스를 일일이 복사하여 붙여넣기로 해야 하므로 필요한 툴이다.
처음에는 엑셀 OLE오토메이션을 이용하는 윈도즈 프로그램(Win32)을 구현하려고 했으나 기술 조사를 해보니 자바스크립트로 엑셀 파일을 다루는 라이브러리(xlsx)가 있어서 그거를 이용했다.
소스 편집 도구는 전에는 ATOM을 이용했으나 이번에는 비주얼 스튜디오 커뮤니티 버전을 이용했다. 노드 프로젝트를 생성할 수 있고, F5로 변수 디버깅도 가능했다. 좋은 도구다.
필요한 라이브러리 설치한다. xlsx-workbook 라이브러리는 xlsx를 좀 더 쉽게 사용하도록 랩핑한 라이브러리이고 예제 샘플만으로도 충분히 사용하기 편했다.
npm install xml2js
npm install hashmap
npm install xlsx-workbook
android_base 서브 모듈의 리소스 폴더와 app 리소스 폴더에서 언어 파일을 각각 읽어와 하나의 엑셀 파일을 생성하는 소스코드이다.
'use strict'; const xml2js = require('xml2js') const hashmap = require('hashmap') const fs = require('fs') const Workbook = require('xlsx-workbook').Workbook; const workbook = new Workbook() const sheet = workbook.add("sheet1") const replaceMap = new hashmap.HashMap() replaceMap.set("%d", "${num}") replaceMap.set("%s", "${str}") replaceMap.set("%1$d", "${num1}") replaceMap.set("%2$d", "${num2}") replaceMap.set("%3$d", "${num3}") replaceMap.set("%4$d", "${num4}") replaceMap.set("%1$s", "${str1}") replaceMap.set("%2$s", "${str2}") replaceMap.set("%3$s", "${str3}") replaceMap.set("%4$s", "${str4}") const replaceKeys = replaceMap.keys() const langColumnIndexMap = new hashmap.HashMap() langColumnIndexMap.set("en", 4) langColumnIndexMap.set("ko", 5) langColumnIndexMap.set("ru", 6) langColumnIndexMap.set("de", 7) function read_resouce_dir(res_dir) { console.log("\n" + res_dir) fs.readdirSync(res_dir).forEach(filename => { if (filename == "values" || filename.startsWith("values-")) { const langCode = filename == "values" ? "en" : filename.substr(7) if (langCode.length == 2) { console.log(filename + " -> " + langCode) const langColIndex = langColumnIndexMap.get(langCode) sheet[2][langColIndex] = langCode const xmlPath = res_dir + filename + "/strings.xml" write_language(langCode, langColIndex, xmlPath) } } }) } const stringId2RowIndexMap = new hashmap.HashMap() let nextStringIdRowIndex = 4 function write_language(langCode, langColIndex, xmlPath) { const xmlString = fs.readFileSync(xmlPath) const parser = new xml2js.Parser() parser.parseString(xmlString, function (err, result) { const len = result.resources.string.length for (let i = 0; i < len; i++) { const item = result.resources.string[i] const id = item.$.name let row if (stringId2RowIndexMap.has(id)) { row = stringId2RowIndexMap.get(id) } else { row = nextStringIdRowIndex++ stringId2RowIndexMap.set(id, row) sheet[row][0] = id } write_string(langCode, row, langColIndex, item._) } }) } const mustHaveColIndex = 3 function write_string(langCode, row, col, value) { let mustHave = "" for (let i = 0; i < replaceKeys.length; i++) { const str0 = replaceKeys[i] const index = value.indexOf(str0) if (index == 0 || (index > 0 && value[index - 1] != '%')) { const str1 = replaceMap.get(str0) mustHave += " " + str1 value = value.replace(str0, str1) } } if (langCode == "en" && mustHave.length > 0) { sheet[row][mustHaveColIndex] = mustHave.substr(1) } sheet[row][col] = value } const android_base_res_dir = "c:\\Project\\screenshot\\android_base\\res\\" const app_res_dir = "c:\\Project\\screenshot\\app\\src\\main\\res\\" sheet[0][0] = "Screenshot touch translation" sheet[3][0] = "[common]" read_resouce_dir(android_base_res_dir) nextStringIdRowIndex++ sheet[nextStringIdRowIndex++][0] = "[app]" read_resouce_dir(app_res_dir) workbook.save("Screenshot_touch")
안드로이드 언어 파일에는 %1$d와 같은 특수한 기호가 있는데, 이것은 번역자가 이해하기 어렵고 오타가 많기 때문에 ${num1} 처럼 사용하기 쉬운 키워드로 변경했다. 그리고 이것을 Must have 컬럼에 기록해서 나중에 번역이 제대로 되었는지 점검할 때 사용한다.
최종 생성된 엑셀 파일은 다음과 같다.
이 파일에 설명을 추가하고 구글독스에 올리면 1단계는 완료된다. 다음 번에는 번역자가 실제로 번역을 테스트할 수 있는 도구를 구현하겠다.