Extract win32 localatiztion to a new file win32locale.py.

This commit is contained in:
John Ralls 2023-07-16 12:20:31 -07:00 committed by Nick Hall
parent 354e191ea0
commit 7051d3bf13
3 changed files with 247 additions and 207 deletions

View File

@ -48,8 +48,10 @@ if sys.platform == 'darwin':
from . maclocale import mac_setup_localization
if sys.platform == 'win32':
from ctypes import cdll
from .win32locale import (win32_locale_init_from_env,
win32_locale_bindtextdomain)
from .win32locale import _LOCALE_NAMES
LOG = logging.getLogger("." + __name__)
LOG.propagate = True
@ -76,101 +78,11 @@ except ImportError:
ICU_LOCALES = None
if HAVE_ICU:
ICU_LOCALES = Locale.getAvailableLocales()
# Map of languages for converting to Microsoft locales and naming
# locales for display to the user. It's important to add to this list
# when a new translation is added. Note the dummy _(): That's just to
# get xgettext to include the string in gramps.pot; actual translation
# is done in _get_language_string() below.
# (The gramps officially-supported language list is ALL_LINGUAS in setup.py)
def _(msgid):
return msgid
_LOCALE_NAMES = {
'ar': ('Arabic_Saudi Arabia', '1256', _("Arabic")),
'bg': ('Bulgrian_Bulgaria', '1251', _("Bulgarian")),
#Windows has no translation for Breton
'br': (None, None, _("Breton")),
'ca': ('Catalan_Spain', '1252', _("Catalan")),
'cs': ('Czech_Czech Republic', '1250', _("Czech")),
'da': ('Danish_Denmark', '1252', _("Danish")),
'de': ('German_Germany', '1252', _("German")),
'de_AT': ('German_Austria', '1252', _("German (Austria)")),
'el': ('Greek_Greece', '1253', _("Greek")),
'en': ('English_United States', '1252', _("English (USA)")),
'en_GB': ('English_United Kingdom', '1252', _("English")),
#Windows has no translation for Esperanto
'eo': (None, None, _("Esperanto")),
'es': ('Spanish_Spain', '1252', _("Spanish")),
'fi': ('Finnish_Finland', '1252', _("Finnish")),
'fr': ('French_France', '1252', _("French")),
#Windows has no translation for Gaelic
'ga': (None, None, _("Gaelic")),
'he': ('Hebrew_Israel', '1255', _("Hebrew")),
'hr': ('Croatian_Croatia', '1250', _("Croatian")),
'hu': ('Hungarian_Hungary', '1250', _("Hungarian")),
'id': ('Indonesian', '1057', _("Indonesian")),
'is': ('Icelandic', '1252', _("Icelandic")),
'it': ('Italian_Italy', '1252', _("Italian")),
'ja': ('Japanese_Japan', '932', _("Japanese")),
'lt': ('Lithuanian_Lithuania', '1252', _("Lithuanian")),
#Windows has no translation for Macedonian
'mk': (None, None, _("Macedonian")),
'nb': ('Norwegian_Norway', '1252', _("Norwegian Bokmal")),
'nl': ('Dutch_Netherlands', '1252', _("Dutch")),
'nn': ('Norwegian-Nynorsk_Norway', '1252', _("Norwegian Nynorsk")),
'pl': ('Polish_Poland', '1250', _("Polish")),
'pt_BR': ('Portuguese_Brazil', '1252', _("Portuguese (Brazil)")),
'pt_PT': ('Portuguese_Portugal', '1252', _("Portuguese (Portugal)")),
'ro': ('Romanian_Romania', '1250', _("Romanian")),
'ru': ('Russian_Russia', '1251', _("Russian")),
'sk': ('Slovak_Slovakia', '1250', _("Slovak"),),
'sl': ('Slovenian_Slovenia', '1250', _("Slovenian")),
'sq': ('Albanian_Albania', '1250', _("Albanian")),
'sr': ('Serbian(Cyrillic)_Serbia and Montenegro', '1251', _("Serbian")),
'sv': ('Swedish_Sweden', '1252', _("Swedish")),
# Windows has no codepage for Tamil
'ta': (None, None, _("Tamil")),
'tr': ('Turkish_Turkey', '1254', _("Turkish")),
'uk': ('Ukrainian_Ukraine', '1251', _("Ukrainian")),
'vi': ('Vietnamese_Vietnam', '1258', _("Vietnamese")),
'zh_CN': ('Chinese_China', '936', _("Chinese (Simplified)")),
'zh_HK': ('Chinese_Hong Kong', '950', _("Chinese (Hong Kong)")),
'zh_TW': ('Chinese_Taiwan', '950', _("Chinese (Traditional)")),
}
# locales with right-to-left text
_RTL_LOCALES = ('ar', 'he')
# locales with less than 70% currently translated
INCOMPLETE_TRANSLATIONS = ('ar', 'bg', 'sq', 'ta', 'tr', 'zh_HK', 'zh_TW')
def _check_mswin_locale(loc):
msloc = None
try:
msloc = _LOCALE_NAMES[loc[:5]][:2]
newloc = loc[:5]
except KeyError:
try:
msloc = _LOCALE_NAMES[loc[:2]][:2]
newloc = loc[:2]
except KeyError:
#US English is the outlier, all other English locales want
#real English:
if loc[:2] == ('en') and loc[:5] != 'en_US':
return ('en_GB', '1252')
return (None, None)
return (newloc, msloc)
def _check_mswin_locale_reverse(loc):
for (newloc, msloc) in _LOCALE_NAMES.items():
if msloc and newloc == msloc[0]:
return (newloc, msloc[1])
#US English is the outlier, all other English locales want real English:
if loc.startswith('English') and loc != 'English_United States':
return ('en_GB', '1252')
return (None, None)
def _check_gformat():
"""
Some OS environments do not support the locale.nl_langinfo() method
@ -264,96 +176,8 @@ class GrampsLocale:
matches = matches and (lang is None or self.lang == lang)
matches = matches and (domain is None or self.localedomain == domain)
return matches and (languages is None or len(languages) == 0 or
self.languages == languages)
self.language == languages)
def _win_init_environment(self):
"""
The Windows implementation of Python ignores environment
variables when setting the locale; it only pays attention to
the control panel language settings -- which for practical
purposes limits one to the language for which one purchased
Windows. This function enables using alternative
localizations.
"""
if 'LANG' in os.environ:
(lang, loc) = _check_mswin_locale(os.environ['LANG'])
if loc:
locale.setlocale(locale.LC_ALL, '.'.join(loc))
self.lang = lang
self.encoding = loc[1]
else:
LOG.debug("%%LANG%% value %s not usable", os.environ['LANG'])
if not self.lang:
locale.setlocale(locale.LC_ALL, '')
locale_tuple = locale.getlocale()
lang = locale_tuple[0]
loc = _check_mswin_locale_reverse(lang)
if loc[0]:
self.lang = loc[0]
self.encoding = loc[1]
else:
(lang, loc) = _check_mswin_locale(locale.getlocale()[0])
if lang:
self.lang = lang
self.encoding = loc[1]
else:
LOG.debug("No usable locale found in environment")
if not self.lang:
self.lang = 'C'
self.encoding = 'cp1252'
if 'LC_MESSAGES' in os.environ:
lang = self.check_available_translations(os.environ['LC_MESSAGES'])
if lang:
self.language = [lang]
else:
LOG.debug("No translation for %%LC_MESSAGES%% locale")
if 'LANGUAGE' in os.environ:
language = [x for x in [self.check_available_translations(l)
for l in os.environ["LANGUAGE"].split(":")]
if x]
if language:
self.language = language
else:
LOG.debug("No languages with translations found "
"in %%LANGUAGES%%")
if not self.language:
self.language = [self.lang[:5]]
if 'COLLATION' in os.environ:
coll = os.environ['COLLATION']
if HAVE_ICU:
if coll[:2] in ICU_LOCALES:
self.collation = coll
else:
self.collation = self.lang
else:
(coll, loc) = _check_mswin_locale(coll)
if not loc:
(coll, loc) = _check_mswin_locale(self.lang)
self.collation = '.'.join(loc)
locale.setlocale(locale.LC_COLLATE, self.collation )
else:
if HAVE_ICU:
self.collation = self.lang
else:
(coll, loc) = _check_mswin_locale(self.lang)
if loc:
self.collation = '.'.join(loc)
else:
self.collation = 'C'
locale.setlocale(locale.LC_COLLATE, self.collation )
# We can't import datahandler stuff or we'll get a circular
# dependency, so we rely on the available translations list
if 'LC_TIME' in os.environ:
self.calendar = (
self.check_available_translations(os.environ['LC_TIME']) or
self.lang)
else:
self.calendar = self.lang
def _init_from_environment(self):
@ -439,21 +263,6 @@ class GrampsLocale:
LOG.debug("The locale tformat for '%s' is '%s'",
self.lang, _check_gformat())
def _win_bindtextdomain(self, localedomain, localedir):
"""
Help routine for loading and setting up libintl attributes
Returns libintl
"""
try:
libintl = cdll.LoadLibrary('libintl-8')
libintl.bindtextdomain(localedomain, localedir)
libintl.textdomain(localedomain)
libintl.bind_textdomain_codeset(localedomain, "UTF-8")
except WindowsError:
LOG.warning("Localization library libintl not on %PATH%, "
"localization will be incomplete")
def __init_first_instance(self):
"""
Initialize the primary locale from whatever might be
@ -477,7 +286,7 @@ class GrampsLocale:
if sys.platform == 'darwin':
mac_setup_localization(self)
elif sys.platform == 'win32':
self._win_init_environment()
win32_locale_init_from_env(self, HAVE_ICU, ICU_LOCALES)
else:
self._init_from_environment()
else:
@ -540,13 +349,13 @@ class GrampsLocale:
# GtkBuilder uses GLib's g_dgettext wrapper, which oddly is bound
# with locale instead of gettext. Win32 doesn't support bindtextdomain.
if self.localedir:
if sys.platform != 'win32':
if sys.platform == 'win32':
win32_locale_bindtextdomain(self.localedomain.encode('utf-8'),
self.localedir.encode('utf-8'))
else:
# bug12278, _build_popup_ui() under linux and macOS
locale.textdomain(self.localedomain)
locale.bindtextdomain(self.localedomain, self.localedir)
else:
self._win_bindtextdomain(self.localedomain.encode('utf-8'),
self.localedir.encode('utf-8'))
self.rtl_locale = False
if self.language[0] in _RTL_LOCALES:
@ -895,21 +704,21 @@ class GrampsLocale:
"""
if not self.localedir:
return None
#Note that this isn't a typo for self.language; self.languages
#Note that this isn't a typo for self.language; self.language
#is cached so we don't have to query the file system every
#time this function is called.
if not hasattr(self, 'languages'):
self.languages = self.get_available_translations()
self.language = self.get_available_translations()
if not loc:
return None
if loc[:5] in self.languages:
if loc[:5] in self.language:
return loc[:5]
#US English is the outlier, all other English locales want real English:
if loc[:2] == 'en' and loc[:5] != 'en_US':
return 'en_GB'
if loc[:2] in self.languages:
if loc[:2] in self.language:
return loc[:2]
return None

View File

@ -2,9 +2,7 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Brian G. Matherly
# Copyright (C) 2013 John Ralls
# Copyright (C) 2023 John Ralls
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by

View File

@ -0,0 +1,233 @@
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2023 John Ralls
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
win32locale provides l18n setup for the Microsoft Windows platform.
"""
import os
import locale
from ctypes import cdll
import logging
LOG = logging.getLogger("." + __name__)
LOG.propagate = True
# Map of languages for converting to Microsoft locales and naming
# locales for display to the user. It's important to add to this list
# when a new translation is added. Note the dummy _(): That's just to
# get xgettext to include the string in gramps.pot; actual translation
# is done in _get_language_string() below.
# (The gramps officially-supported language list is ALL_LINGUAS in setup.py)
def _(msgid):
return msgid
_LOCALE_NAMES = {
'ar': ('Arabic_Saudi Arabia', '1256', _("Arabic")),
'bg': ('Bulgrian_Bulgaria', '1251', _("Bulgarian")),
#Windows has no translation for Breton
'br': (None, None, _("Breton")),
'ca': ('Catalan_Spain', '1252', _("Catalan")),
'cs': ('Czech_Czech Republic', '1250', _("Czech")),
'da': ('Danish_Denmark', '1252', _("Danish")),
'de': ('German_Germany', '1252', _("German")),
'de_AT': ('German_Austria', '1252', _("German (Austria)")),
'el': ('Greek_Greece', '1253', _("Greek")),
'en': ('English_United States', '1252', _("English (USA)")),
'en_GB': ('English_United Kingdom', '1252', _("English")),
#Windows has no translation for Esperanto
'eo': (None, None, _("Esperanto")),
'es': ('Spanish_Spain', '1252', _("Spanish")),
'fi': ('Finnish_Finland', '1252', _("Finnish")),
'fr': ('French_France', '1252', _("French")),
#Windows has no translation for Gaelic
'ga': (None, None, _("Gaelic")),
'he': ('Hebrew_Israel', '1255', _("Hebrew")),
'hr': ('Croatian_Croatia', '1250', _("Croatian")),
'hu': ('Hungarian_Hungary', '1250', _("Hungarian")),
'id': ('Indonesian', '1057', _("Indonesian")),
'is': ('Icelandic', '1252', _("Icelandic")),
'it': ('Italian_Italy', '1252', _("Italian")),
'ja': ('Japanese_Japan', '932', _("Japanese")),
'lt': ('Lithuanian_Lithuania', '1252', _("Lithuanian")),
#Windows has no translation for Macedonian
'mk': (None, None, _("Macedonian")),
'nb': ('Norwegian_Norway', '1252', _("Norwegian Bokmal")),
'nl': ('Dutch_Netherlands', '1252', _("Dutch")),
'nn': ('Norwegian-Nynorsk_Norway', '1252', _("Norwegian Nynorsk")),
'pl': ('Polish_Poland', '1250', _("Polish")),
'pt_BR': ('Portuguese_Brazil', '1252', _("Portuguese (Brazil)")),
'pt_PT': ('Portuguese_Portugal', '1252', _("Portuguese (Portugal)")),
'ro': ('Romanian_Romania', '1250', _("Romanian")),
'ru': ('Russian_Russia', '1251', _("Russian")),
'sk': ('Slovak_Slovakia', '1250', _("Slovak"),),
'sl': ('Slovenian_Slovenia', '1250', _("Slovenian")),
'sq': ('Albanian_Albania', '1250', _("Albanian")),
'sr': ('Serbian(Cyrillic)_Serbia and Montenegro', '1251', _("Serbian")),
'sv': ('Swedish_Sweden', '1252', _("Swedish")),
# Windows has no codepage for Tamil
'ta': (None, None, _("Tamil")),
'tr': ('Turkish_Turkey', '1254', _("Turkish")),
'uk': ('Ukrainian_Ukraine', '1251', _("Ukrainian")),
'vi': ('Vietnamese_Vietnam', '1258', _("Vietnamese")),
'zh_CN': ('Chinese_China', '936', _("Chinese (Simplified)")),
'zh_HK': ('Chinese_Hong Kong', '950', _("Chinese (Hong Kong)")),
'zh_TW': ('Chinese_Taiwan', '950', _("Chinese (Traditional)")),
}
def _check_mswin_locale(loc):
msloc = None
try:
msloc = _LOCALE_NAMES[loc[:5]][:2]
newloc = loc[:5]
except KeyError:
try:
msloc = _LOCALE_NAMES[loc[:2]][:2]
newloc = loc[:2]
except KeyError:
#US English is the outlier, all other English locales want
#real English:
if loc[:2] == ('en') and loc[:5] != 'en_US':
return ('en_GB', '1252')
return (None, None)
return (newloc, msloc)
def _check_mswin_locale_reverse(loc):
for (newloc, msloc) in _LOCALE_NAMES.items():
if msloc and newloc == msloc[0]:
return (newloc, msloc[1])
#US English is the outlier, all other English locales want real English:
if loc.startswith('English') and loc != 'English_United States':
return ('en_GB', '1252')
return (None, None)
def _set_lang(glocale):
if 'LANG' in os.environ:
(lang, loc) = _check_mswin_locale(os.environ['LANG'])
if loc:
locale.setlocale(locale.LC_ALL, '.'.join(loc))
glocale.lang = lang
glocale.encoding = loc[1]
else:
LOG.debug("%%LANG%% value %s not usable", os.environ['LANG'])
if not glocale.lang:
locale.setlocale(locale.LC_ALL, '')
locale_tuple = locale.getlocale()
lang = locale_tuple[0]
loc = _check_mswin_locale_reverse(lang)
if loc[0]:
glocale.lang = loc[0]
glocale.encoding = loc[1]
else:
(lang, loc) = _check_mswin_locale(locale.getlocale()[0])
if lang:
glocale.lang = lang
glocale.encoding = loc[1]
else:
LOG.debug("No usable locale found in environment")
if not glocale.lang:
glocale.lang = 'C'
glocale.encoding = 'cp1252'
def _set_languages(glocale):
lang = None
if 'LC_MESSAGES' in os.environ:
lang = glocale.check_available_translations(os.environ['LC_MESSAGES'])
if lang:
glocale.language = [lang]
else:
LOG.debug("No translation for %%LC_MESSAGES%% locale")
if 'LANGUAGE' in os.environ:
language = [x for x in [glocale.check_available_translations(l)
for l in os.environ["LANGUAGE"].split(":")]
if x]
if language:
glocale.language = language
else:
LOG.debug("No languages with translations found "
"in %%LANGUAGES%%")
if not glocale.language:
glocale.language = [glocale.lang[:5]]
def _set_collation(glocale, have_icu, icu_locales):
coll = None
if 'COLLATION' in os.environ:
coll = os.environ['COLLATION']
if have_icu:
if coll[:2] in icu_locales:
glocale.collation = coll
else:
glocale.collation = glocale.lang
else:
(coll, loc) = _check_mswin_locale(coll)
if not loc:
(coll, loc) = _check_mswin_locale(glocale.lang)
glocale.collation = '.'.join(loc)
locale.setlocale(locale.LC_COLLATE, glocale.collation )
else:
if have_icu:
glocale.collation = glocale.lang
else:
(coll, loc) = _check_mswin_locale(glocale.lang)
if loc:
glocale.collation = '.'.join(loc)
else:
glocale.collation = 'C'
locale.setlocale(locale.LC_COLLATE, glocale.collation )
def _set_calendar(glocale):
# We can't import datahandler stuff or we'll get a circular
# dependency, so we rely on the available translations list
if 'LC_TIME' in os.environ:
glocale.calendar = (
glocale.check_available_translations(os.environ['LC_TIME']) or
glocale.lang)
else:
glocale.calendar = glocale.lang
def win32_locale_init_from_env(glocale, have_icu, icu_locales):
"""
The Windows implementation of Python ignores environment
variables when setting the locale; it only pays attention to
the control panel language settings -- which for practical
purposes limits one to the language for which one purchased
Windows. This function enables using alternative
localizations.
"""
_set_lang(glocale)
_set_languages(glocale)
_set_collation(glocale, have_icu, icu_locales)
_set_calendar(glocale)
def win32_locale_bindtextdomain(localedomain, localedir):
"""
Help routine for loading and setting up libintl attributes
Returns libintl
"""
try:
libintl = cdll.LoadLibrary('libintl-8')
libintl.bindtextdomain(localedomain, localedir)
libintl.textdomain(localedomain)
libintl.bind_textdomain_codeset(localedomain, "UTF-8")
except WindowsError:
LOG.warning("Localization library libintl not on %PATH%, "
"localization will be incomplete")