NSLocalizedString

Here is the scenario:

  • I am writing an iPhone application using NSLocalizedString, and I decide to release it in different countries.
  • I decided to publish the application in France.
  • The translator takes my Localized.strings and performs the translation perfectly
  • I am updating the application and need some translation.

I use genstrings and it overwrites the good work the translator has done, is there an easy way to manage my translations over application versions?

+4
source share
5 answers

genstrings this GitHub project that provides python scripts that make genstrings little smarter.

Since I don’t like only link-links (links may die), I will also write a python script here (all credits are transferred to the author of the linked project)

 #!/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 Pierre Dulac 2012 http://friendcashapp.com # Changes # - use logging instead of print # Adds # - MIT Licence # - the first parameter in the command line to specify the path of *.lproj directories # - an optional paramter to control the debug level (set to info by default) # Fixes # - do not convert a file if it is already in utf-8 # - allow multiline translations generated by genstrings by modifing the re_translation regex # - # MIT Licence # # Copyright (C) 2012 Pierre Dulac # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and # associated documentation files (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all copies or substantial # portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from sys import argv from codecs import open from re import compile from copy import copy import os import shutil import optparse import logging logging.getLogger().level = logging.INFO __version__ = "0.1" __license__ = "MIT" USAGE = "%prog [options] <url>" VERSION = "%prog v" + __version__ re_translation = compile(r'^"((?:[^"]|\\")+)" = "((?:[^"]|\\")+)";(?:\n)?$') 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.reset() if auto_read: self.read_from_file(fname) def reset(self): self.strings = [] self.strings_d = {} def read_from_file(self, fname=None): self.reset() fname = self.fname if fname == None else fname try: #f = open(fname, encoding='utf_8', mode='r') f = open(fname, encoding='utf_8', mode='r') except: print 'File %s does not exist.' % fname exit(-1) try: line = f.readline() logging.debug(line) except: logging.error("Can't read line for file: %s" % fname) raise i = 1 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() i += 1 # handle multi lines while len(line) > 1 and line[-2] != u';': line += f.readline() i += 1 logging.debug("%d %s" % (i, line.rstrip('\n'))) if line and re_translation.match(line): translation = line else: logging.error("Line %d of file '%s' raising the exception: %s" % (i, self.fname, line)) raise Exception('invalid file') line = f.readline() i += 1 while line and line == u'\n': line = f.readline() i += 1 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) # sort by key self.strings.sort(key=lambda item: item.key) 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 update_with(self, new): for string in new.strings: if not self.strings_d.has_key(string.key): self.strings.append(string) self.strings_d[string.key] = string 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 Exception, inst: logging.error('Error: input files have invalid format.') raise STRINGS_FILE = 'Localizable.strings' def localize(path, excluded_paths): languages = [os.path.join(path,name) for name in os.listdir(path) if name.endswith('.lproj') and os.path.isdir(os.path.join(path,name))] print "languages found", languages for language in languages: original = merged = language + os.path.sep + STRINGS_FILE old = original + '.old' new = original + '.new' if os.path.isfile(original): try: open(original, encoding='utf_8', mode='r').read() os.rename(original, old) except: os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (original, old)) # gen os.system('find %s -name \*.m -not -path "%s" | xargs genstrings -q -o "%s"' % (path, excluded_paths, language)) try: open(original, encoding='utf_8', mode='r').read() shutil.copy(original, new) except: os.system('iconv -f UTF-16 -t UTF-8 "%s" > "%s"' % (original, new)) # merge merge(merged, old, new) logging.info("Job done for language: %s" % language) else: os.system('genstrings -q -o "%s" `find %s -name "*.m" -not -path "%s"`' % (language, path, excluded_paths)) os.rename(original, old) try: open(old, encoding='utf_8', mode='r').read() except: 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 parse_options(): """parse_options() -> opts, args Parse any command-line options given returning both the parsed options and arguments. """ parser = optparse.OptionParser(usage=USAGE, version=VERSION) parser.add_option("-d", "--debug", action="store_true", default=False, dest="debug", help="Set to DEBUG the logging level (default to INFO)") parser.add_option("-p", "--path", action="store", type="str", default=os.getcwd(), dest="path", help="Path (relative or absolute) to use for searching for *.lproj directories") parser.add_option("-e", "--exclude", action="store", type="str", default=None, dest="excluded_paths", help="Regex for paths to exclude ex. ``./Folder1/*``") opts, args = parser.parse_args() return opts, args if __name__ == '__main__': opts, args = parse_options() if opts.debug: logging.getLogger().level = logging.DEBUG if opts.path: opts.path = os.path.realpath(opts.path) if opts.excluded_paths: opts.excluded_paths = os.path.realpath(opts.excluded_paths) logging.info("Running the script on path %s" % opts.path) localize(opts.path, opts.excluded_paths) 
+5
source

I use:

http://www.loc-suite.com

Only for translation of new parts

+3
source

I really started using a tool called PhraseApp https://phraseapp.com/projects

It is worth exploring if you need to localize the application!

0
source

I had a similar problem. I changed a lot of keys for my NSLocalizedString macros and was frightened that I sent the application with missing translations (I did not want to start the entire application manually and check if everything was there ...).

I tried the github project that Gabriella Petronella published, but I was not very happy with it, so I wrote my own python module to accomplish what I wanted to do. (I will not publish the code here, since this is a whole module and not only one script: D)

0
source

Here are a few options you can choose:

  • You can use some hand-written solutions, such as the script mentioned above, that will not completely overwrite old files when adding recently translated lines to them.
  • You can also create an additional strings.h file that will contain all the lines you have, so you don’t have to rewrite them all the time, just in one place. Therefore, genstrings are no longer required. However, it makes sense to use this: the string.h file will be unstructured, which is probably not convenient for large projects.

Thanks to Best Practices for Using NSLocalizedString

 // In strings.h #define YOUR_STRING_KEY NSLocalizedString(@"Cancel", nil) // Somewhere else in you code NSLog(@"%@", YOUR_STRING_KEY); 
0
source

Source: https://habr.com/ru/post/1488478/


All Articles