1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 23:37:14 +03:00
2017-08-19 16:03:39 +01:00

249 lines
9.9 KiB
Python
Executable File

#!/usr/bin/python
#
# Localization with gettext is a pain on non-Unix systems. Use OSX-style strings files instead.
#
import codecs
from collections import OrderedDict
import numbers
import os
from os.path import basename, dirname, isfile, join, normpath
import re
import sys
from sys import platform
import __builtin__
import locale
locale.setlocale(locale.LC_ALL, '')
# Language name
LANGUAGE_ID = '!Language'
if platform == 'darwin':
from Foundation import NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
elif platform == 'win32':
import ctypes
from ctypes.wintypes import *
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd318124%28v=vs.85%29.aspx
MUI_LANGUAGE_ID = 4
MUI_LANGUAGE_NAME = 8
GetUserPreferredUILanguages = ctypes.windll.kernel32.GetUserPreferredUILanguages
GetUserPreferredUILanguages.argtypes = [ DWORD, ctypes.POINTER(ctypes.c_ulong), LPCVOID, ctypes.POINTER(ctypes.c_ulong) ]
GetUserPreferredUILanguages.restype = BOOL
LOCALE_NAME_USER_DEFAULT = None
GetNumberFormatEx = ctypes.windll.kernel32.GetNumberFormatEx
GetNumberFormatEx.argtypes = [LPCWSTR, DWORD, LPCWSTR, LPCVOID, LPWSTR, ctypes.c_int]
GetNumberFormatEx.restype = ctypes.c_int
else: # POSIX
import locale
class Translations:
FALLBACK = 'en' # strings in this code are in English
FALLBACK_NAME = 'English'
TRANS_RE = re.compile(r'\s*"((?:[^"]|(?:\"))+)"\s*=\s*"((?:[^"]|(?:\"))+)"\s*;\s*$')
COMMENT_RE = re.compile(r'\s*/\*.*\*/\s*$')
def __init__(self):
self.translations = {}
def install_dummy(self):
# For when translation is not desired or not available
self.translations = {} # not used
__builtin__.__dict__['_'] = lambda x: unicode(x).replace(ur'\"', u'"').replace(u'{CR}', u'\n') # Promote strings to Unicode for consistency
def install(self, lang=None):
available = self.available()
available.add(Translations.FALLBACK)
if not lang:
# Choose the default language
for preferred in Locale.preferredLanguages():
components = preferred.split('-')
if preferred in available:
lang = preferred
elif '-'.join(components[0:2]) in available:
lang = '-'.join(components[0:2]) # language-script
elif components[0] in available:
lang = components[0] # just base language
if lang:
break
if lang not in self.available():
self.install_dummy()
else:
self.translations = self.contents(lang)
__builtin__.__dict__['_'] = self.translate
def contents(self, lang):
assert lang in self.available()
translations = {}
with self.file(lang) as h:
for line in h:
if line.strip():
match = Translations.TRANS_RE.match(line)
if match:
translations[match.group(1).replace(ur'\"', u'"')] = match.group(2).replace(ur'\"', u'"').replace(u'{CR}', u'\n')
elif __debug__ and not Translations.COMMENT_RE.match(line):
print 'Bad translation: %s' % line.strip()
if translations.get(LANGUAGE_ID, LANGUAGE_ID) == LANGUAGE_ID:
translations[LANGUAGE_ID] = unicode(lang) # Replace language name with code if missing
return translations
def translate(self, x):
if __debug__:
if x not in self.translations:
print 'Missing translation: "%s"' % x
return self.translations.get(x) or unicode(x).replace(ur'\"', u'"').replace(u'{CR}', u'\n')
# 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
# Available language names by code
def available_names(self):
names = OrderedDict([
(None, _('Default')), # Appearance theme and language setting
])
names.update(sorted([(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] +
[(Translations.FALLBACK, Translations.FALLBACK_NAME)],
key=lambda x: x[1])) # Sort by name
return names
def respath(self):
if getattr(sys, 'frozen', False):
if platform=='darwin':
return normpath(join(dirname(sys.executable.decode(sys.getfilesystemencoding())), os.pardir, 'Resources'))
else:
return join(dirname(sys.executable.decode(sys.getfilesystemencoding())), 'L10n')
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')
class Locale:
def __init__(self):
if platform=='darwin':
self.int_formatter = NSNumberFormatter.alloc().init()
self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
self.float_formatter = NSNumberFormatter.alloc().init()
self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
self.float_formatter.setMinimumFractionDigits_(5)
self.float_formatter.setMaximumFractionDigits_(5)
def stringFromNumber(self, number, decimals=None):
# Uses the current system locale, irrespective of language choice.
# Unless `decimals` is specified, the number will be formatted with 5 decimal
# places if the input is a float, or none if the input is an int.
if decimals == 0 and not isinstance(number, numbers.Integral):
number = int(round(number))
if platform == 'darwin':
if not decimals and isinstance(number, numbers.Integral):
return self.int_formatter.stringFromNumber_(number)
else:
self.float_formatter.setMinimumFractionDigits_(decimals or 5)
self.float_formatter.setMaximumFractionDigits_(decimals or 5)
return self.float_formatter.stringFromNumber_(number)
else:
if not decimals and isinstance(number, numbers.Integral):
return locale.format('%d', number, True)
else:
return locale.format('%.*f', (decimals or 5, number), True)
def numberFromString(self, string):
# Uses the current system locale, irrespective of language choice.
# Returns None if the string is not parsable, otherwise an integer or float.
if platform=='darwin':
return self.float_formatter.numberFromString_(string)
else:
try:
return locale.atoi(string)
except:
try:
return locale.atof(string)
except:
return None
# Returns list of preferred language codes in RFC4646 format i.e. "lang[-script][-region]"
# Where lang is a lowercase 2 alpha ISO 639-1 or 3 alpha ISO 639-2 code,
# script is a capitalized 4 alpha ISO 15924 code and region is an uppercase 2 alpha ISO 3166 code
def preferredLanguages(self):
if platform=='darwin':
return 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
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 wszarray_to_list(buf)
return None
else: # POSIX
lang = locale.getlocale()[0]
return lang and [lang.replace('_','-')]
# singleton
Locale = Locale()
# 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]?(['"])(((?<!\\)\\\1|.)+?)\1\)[^#]*(#.+)?''') # match a single line python literal
seen = {}
for f in (sorted([x for x in os.listdir('.') if x.endswith('.py')]) +
sorted([join('plugins', x) for x in os.listdir('plugins') if x.endswith('.py')])):
with codecs.open(f, 'r', 'utf-8') as h:
lineno = 0
for line in h:
lineno += 1
match = regexp.search(line)
if match and not seen.get(match.group(2)): # only record first commented instance of a string
seen[match.group(2)] = (match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + '[%s]' % basename(f)
if seen:
template = codecs.open('L10n/en.template', 'w', 'utf-8')
template.write('/* Language name */\n"%s" = "%s";\n\n' % (LANGUAGE_ID, 'English'))
for thing in sorted(seen, key=unicode.lower):
if seen[thing]:
template.write('/* %s */\n' % (seen[thing]))
template.write('"%s" = "%s";\n\n' % (thing, thing))
template.close()