-
-
Save yoichitgy/29bdd71c3556c2055cc0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Localize.py - Incremental localization on XCode projects | |
# João Moreno 2009 | |
# http://joaomoreno.com/ | |
# Modified by Steve Streeting 2010 http://www.stevestreeting.com | |
# Changes | |
# - Use .strings files encoded as UTF-8 | |
# This is useful because Mercurial and Git treat UTF-16 as binary and can't | |
# diff/merge them. For use on iPhone you can run an iconv script during build to | |
# convert back to UTF-16 (Mac OS X will happily use UTF-8 .strings files). | |
# - Clean up .old and .new files once we're done | |
# Modified by Yoichi Tagaya 2015 http://github.com/yoichitgy | |
# Changes | |
# - Use command line arguments to execute as `mergegenstrings.py path routine` | |
# path: Path to the directory containing source files and lproj directories. | |
# routine: Routine argument for genstrings command specified with '-s' option. | |
# - Support both .swift and .m files. | |
# - Support .storyboard and .xib files. | |
from sys import argv | |
from codecs import open | |
from re import compile | |
from copy import copy | |
import os | |
re_translation = compile(r'^"(.+)" = "(.+)";$') | |
re_comment_single = compile(r'^/\*.*\*/$') | |
re_comment_start = compile(r'^/\*.*$') | |
re_comment_end = compile(r'^.*\*/$') | |
class LocalizedString(): | |
def __init__(self, comments, translation): | |
self.comments, self.translation = comments, translation | |
self.key, self.value = re_translation.match(self.translation).groups() | |
def __unicode__(self): | |
return u'%s%s\n' % (u''.join(self.comments), self.translation) | |
class LocalizedFile(): | |
def __init__(self, fname=None, auto_read=False): | |
self.fname = fname | |
self.strings = [] | |
self.strings_d = {} | |
if auto_read: | |
self.read_from_file(fname) | |
def read_from_file(self, fname=None): | |
fname = self.fname if fname == None else fname | |
try: | |
f = open(fname, encoding='utf_8', mode='r') | |
except: | |
print 'File %s does not exist.' % fname | |
exit(-1) | |
line = f.readline() | |
while line: | |
comments = [line] | |
if not re_comment_single.match(line): | |
while line and not re_comment_end.match(line): | |
line = f.readline() | |
comments.append(line) | |
line = f.readline() | |
if line and re_translation.match(line): | |
translation = line | |
else: | |
raise Exception('invalid file') | |
line = f.readline() | |
while line and line == u'\n': | |
line = f.readline() | |
string = LocalizedString(comments, translation) | |
self.strings.append(string) | |
self.strings_d[string.key] = string | |
f.close() | |
def save_to_file(self, fname=None): | |
fname = self.fname if fname == None else fname | |
try: | |
f = open(fname, encoding='utf_8', mode='w') | |
except: | |
print 'Couldn\'t open file %s.' % fname | |
exit(-1) | |
for string in self.strings: | |
f.write(string.__unicode__()) | |
f.close() | |
def merge_with(self, new): | |
merged = LocalizedFile() | |
for string in new.strings: | |
if self.strings_d.has_key(string.key): | |
new_string = copy(self.strings_d[string.key]) | |
new_string.comments = string.comments | |
string = new_string | |
merged.strings.append(string) | |
merged.strings_d[string.key] = string | |
return merged | |
def merge(merged_fname, old_fname, new_fname): | |
try: | |
old = LocalizedFile(old_fname, auto_read=True) | |
new = LocalizedFile(new_fname, auto_read=True) | |
merged = old.merge_with(new) | |
merged.save_to_file(merged_fname) | |
except: | |
print 'Error: input files have invalid format.' | |
STRINGS_FILE = 'Localizable.strings' | |
def localizeCode(path, routine): | |
print 'Localize source code...' | |
languages = [lang for lang in [os.path.join(path, name) for name in os.listdir(path)] | |
if lang.endswith('.lproj') and os.path.isdir(lang)] | |
for language in languages: | |
print language | |
original = merged = os.path.join(language, STRINGS_FILE) | |
old = original + '.old' | |
new = original + '.new' | |
if os.path.isfile(original): | |
os.rename(original, old) | |
os.system('genstrings -q -s %s -o "%s" `find %s -name "*.swift" -o -name "*.m"`' % (routine, language, path)) | |
os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (original, new)) | |
merge(merged, old, new) | |
else: | |
os.system('genstrings -q -s %s -o "%s" `find %s -name "*.swift" -o -name "*.m"`' % (routine, language, path)) | |
os.rename(original, old) | |
os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (old, original)) | |
if os.path.isfile(old): | |
os.remove(old) | |
if os.path.isfile(new): | |
os.remove(new) | |
def localizeInterface(path, developmentLanguage): | |
baseDir = os.path.join(path, "Base.lproj") | |
developmentLanguage = os.path.splitext(developmentLanguage)[0] + ".lproj" # Add the extension if not exists | |
print developmentLanguage | |
if os.path.isdir(baseDir): | |
print 'Localize interface...' | |
ibFileNames = [name for name in os.listdir(baseDir) if name.endswith('.storyboard') or name.endswith('.xib')] | |
languages = [lang for lang in [os.path.join(path, name) for name in os.listdir(path)] | |
if lang.endswith('.lproj') and not lang.endswith('Base.lproj') and os.path.isdir(lang)] | |
for language in languages: | |
print language | |
for ibFileName in ibFileNames: | |
ibFilePath = os.path.join(baseDir, ibFileName) | |
stringsFileName = os.path.splitext(ibFileName)[0] + ".strings" | |
print ' ' + stringsFileName | |
original = merged = os.path.join(language, stringsFileName) | |
old = original + '.old' | |
new = original + '.new' | |
if os.path.isfile(original) and not language.endswith(developmentLanguage): | |
os.rename(original, old) | |
os.system('ibtool --export-strings-file %s %s' % (original, ibFilePath)) | |
os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (original, new)) | |
merge(merged, old, new) | |
else: | |
os.system('ibtool --export-strings-file %s %s' % (original, ibFilePath)) | |
os.rename(original, old) | |
os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (old, original)) | |
if os.path.isfile(old): | |
os.remove(old) | |
if os.path.isfile(new): | |
os.remove(new) | |
if __name__ == '__main__': | |
argc = len(argv) | |
if (argc <= 1 or 4 < argc): | |
print 'Usage: %s path_to_source_directory [routine] [development_language]' % argv[0] | |
quit() | |
path = os.path.abspath(argv[1]) | |
routine = argv[2] if argc > 2 else 'NSLocalizedString' | |
developmentLanguage = argv[3] if argc > 3 else 'en' | |
localizeCode(path, routine) | |
localizeInterface(path, developmentLanguage) |
@maxykato, sorry that I didn't notice your question. The following project has the script set up. Hope it helps.
The script seems to remove all translations for which it cannot find the keys in the code. I have many keys that are generated in the code, eg:
let key = "MenuItem\(index)"
And then in my Localizable.strings:
"MenuItem1" = "Menu item 1";
"MenuItem2" = "Menu item 2";
These items are not recognized and thus removed. Is there anything to solve this? Perhaps a comment like this and then keep everything as is after that comment:
#nongenerated
Or would you suggest placing these kind of localizations in another file?
@lammertw, the genstrings tool on its own is unable to recognize string interpolation and generate strings from that, so I presume that this is intended. These tools work on static code, and cannot know what values are possible. I'd recommend being explicit with your string keys, or putting them in a file you generate manually like you suggested. That said, it would be nice if keys that are not found would be preserved so that others in your situation don't hit any unexpected issues.
@maxykato have you find any fixes for you?
For anyone having issues with iconv while converting your files. I recommend modifying the script to use vim instead.
That worked for me.
@rajohns08, thanks for the report. As far as I checked script with Xcode 7 beta 5 and 6, it works as intended. It might a problem of earlier beta versions of Xcode 7.