#!/usr/bin/python # # Localization with gettext is a pain on non-Unix systems. Use OSX-style strings files instead. # import codecs import os from os.path import dirname, isfile, join, normpath import re import sys from sys import platform import __builtin__ class Translations: FALLBACK = 'en' # strings in this code are in English def __init__(self): self.translations = {} def install(self): path = join(self.respath(), 'L10n') available = self.available() available.add(Translations.FALLBACK) for preferred in self.preferred(): if preferred in available: lang = preferred break else: for preferred in self.preferred(): preferred = preferred.split('-',1)[0] # just base language if preferred in available: lang = preferred break else: lang = Translations.FALLBACK if lang not in self.available(): __builtin__.__dict__['_'] = lambda x: x else: regexp = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$') comment= re.compile(r'\s*/\*.*\*/\s*$') with self.file(lang) as h: for line in h: if line.strip(): match = regexp.match(line) if match: self.translations[match.group(1)] = match.group(2) elif not comment.match(line): assert match, 'Bad translation: %s' % line __builtin__.__dict__['_'] = self.translate if __debug__: def translate(self, x): if x in self.translations: return self.translations[x] else: print 'Missing translation: "%s"' % x else: def translate(self, x): return self.translations.get(x, x) # Returns list of available language codes def available(self): path = self.respath() if getattr(sys, 'frozen', False) and platform=='darwin': available = set([x[:-len('.lproj')] for x in os.listdir(path) if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))]) else: available = set([x[:-len('.strings')] for x in os.listdir(path) if x.endswith('.strings')]) return available # Returns list of preferred language codes in lowercase RFC4646 format. # Typically "lang[-script][-region]" where lang is a 2 alpha ISO 639-1 or 3 alpha ISO 639-2 code, # script is a 4 alpha ISO 15924 code and region is a 2 alpha ISO 3166 code def preferred(self): if platform=='darwin': from Foundation import NSLocale return [x.lower() for x in NSLocale.preferredLanguages()] or None elif platform=='win32': def wszarray_to_list(array): offset = 0 while offset < len(array): sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2) if sz: yield sz offset += len(sz)+1 else: break # https://msdn.microsoft.com/en-us/library/windows/desktop/dd318124%28v=vs.85%29.aspx import ctypes MUI_LANGUAGE_ID = 4 MUI_LANGUAGE_NAME = 8 GetUserPreferredUILanguages = ctypes.windll.kernel32.GetUserPreferredUILanguages num = ctypes.c_ulong() size = ctypes.c_ulong(0) if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), None, ctypes.byref(size)) and size.value): buf = ctypes.create_unicode_buffer(size.value) if GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), ctypes.byref(buf), ctypes.byref(size)): return [x.lower() for x in wszarray_to_list(buf)] return None else: # POSIX import locale lang = locale.getdefaultlocale()[0] return lang and [lang.replace('_','-').lower()] def respath(self): if getattr(sys, 'frozen', False): if platform=='darwin': return normpath(join(dirname(sys.executable), os.pardir, 'Resources')) else: return dirname(sys.executable) elif __file__: return join(dirname(__file__), 'L10n') else: return 'L10n' def file(self, lang): if getattr(sys, 'frozen', False) and platform=='darwin': return codecs.open(join(self.respath(), '%s.lproj' % lang, 'Localizable.strings'), 'r', 'utf-16') else: return codecs.open(join(self.respath(), '%s.strings' % lang), 'r', 'utf-8') # generate template strings file - like xgettext # parsing is limited - only single ' or " delimited strings, and only one string per line if __name__ == "__main__": import re regexp = re.compile(r'''_\([ur]?(['"])(((?