Get the available translations for anaconda and populate the welcome spoke with them. --- anaconda.spec.in | 1 + pyanaconda/localization.py | 274 +++++++++++++++++++++++++++++++++++ pyanaconda/ui/gui/spokes/welcome.py | 52 +++++-- pyanaconda/ui/gui/spokes/welcome.ui | 4 +- 4 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 pyanaconda/localization.py diff --git a/anaconda.spec.in b/anaconda.spec.in index 31df6d5..682dd89 100644 --- a/anaconda.spec.in +++ b/anaconda.spec.in @@ -112,6 +112,7 @@ Requires: dosfstools Requires: e2fsprogs >= %{e2fsver} Requires: gzip Requires: libarchive +Requires: python-babel %ifarch %{ix86} x86_64 ia64 Requires: dmidecode %endif diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py new file mode 100644 index 0000000..f561557 --- /dev/null +++ b/pyanaconda/localization.py @@ -0,0 +1,274 @@ +# Localization classes and functions +# +# Copyright (C) 2012 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Martin Gracik <mgracik@xxxxxxxxxx> +# + +from collections import defaultdict, deque +import gettext +import locale +import os +import re + +import babel + + +LOCALE_PREFERENCES = {} + + +class LocaleInfo(object): + + def __init__(self, localedata): + self._localedata = localedata + + @property + def language(self): + return self._localedata.language + + @property + def territory(self): + return self._localedata.territory + + @property + def script(self): + return self._localedata.script + + @property + def variant(self): + return self._localedata.variant + + @property + def english_name(self): + return self._localedata.english_name or u'' + + @property + def display_name(self): + # some languages don't have a display_name + display_name = self._localedata.display_name or self.english_name + # some start with lowercase + display_name = display_name.title() + return display_name + + @property + def short_name(self): + return self.__repr__() + + def __repr__(self): + formatstr = '{0.language}' + if self.territory is not None: + formatstr += '_{0.territory}' + if self.script is not None: + formatstr += '@{0.script}' + if self.variant is not None: + formatstr += '#{0.variant}' + + return formatstr.format(self) + + def __str__(self): + return self.english_name.encode('ascii', 'replace') + + def __unicode__(self): + return self.english_name + + def __eq__(self, other): + return repr(self) == repr(other) + + +# XXX this should probably be somewhere else +def partition(seq, func=bool, func_range=(True, False)): + buffers = dict(((x, deque()) for x in func_range)) + + def values(x, seq=iter(seq)): + while True: + while not buffers[x]: + item = seq.next() + buffers[func(item)].append(item) + + yield buffers[x].popleft() + + return tuple(values(x) for x in func_range) + + +def get_all_locales(): + localeset = set() + for localename in sorted(babel.localedata.list()): + try: + localedata = babel.Locale.parse(localename) + except babel.core.UnknownLocaleError: + continue + + locale = LocaleInfo(localedata) + if repr(locale) not in localeset: + localeset.add(repr(locale)) + yield locale + + +def get_available_translations(domain=None, localedir=None): + domain = domain or gettext._current_domain + localedir = localedir or gettext._default_localedir + + langdict = babel.Locale('en', 'US').languages + messagefiles = gettext.find(domain, localedir, langdict.keys(), all=True) + languages = [path.split(os.path.sep)[-3] for path in messagefiles] + + # usually there are no message files for en_US + if 'en_US' not in languages: + languages.append('en_US') + + for langcode in languages: + try: + localedata = babel.Locale.parse(langcode) + except babel.core.UnknownLocaleError: + continue + + yield LocaleInfo(localedata) + + +class PreferredLocale(object): + + @staticmethod + def from_language(language): + locales = defaultdict(set) + for locale in get_all_locales(): + locales[repr(locale)].add(locale) + locales[locale.language].add(locale) + + return PreferredLocale(locales[language]) + + @staticmethod + def from_territory(territory): + locales = defaultdict(set) + for locale in get_all_locales(): + locales[locale.territory].add(locale) + + return PreferredLocale(locales[territory]) + + def __init__(self, localeset): + self._localedict = {repr(locale):locale for locale in localeset} + + def get_all_locales(self, preferences=[]): + preferences = filter(self._localedict.__contains__, preferences) + inside, outside = partition(self._localedict.keys(), func=lambda x: x in preferences) + sorted_locales = [self._localedict[localename] for localename in list(inside) + list(outside)] + return sorted_locales + + def get_preferred_locale(self, preferences=[]): + try: + return self.get_all_locales(preferences)[0] + except IndexError: + return None + + +class Language(object): + + def __init__(self, preferences={}, territory=None): + self.translations = {repr(locale):locale for locale in get_available_translations()} + self.locales = {repr(locale):locale for locale in get_all_locales()} + self.preferred_translation = self.translations['en_US'] + self.preferred_locales = [self.locales['en_US']] + self.preferred_locale = self.preferred_locales[0] + + self.all_preferences = preferences + self.preferences = self.all_preferences.get(territory, []) + self.territory = territory + if self.territory: + self._get_preferred_translation_and_locales() + + def _get_preferred_translation_and_locales(self): + # get locales from territory + locales_from_territory = PreferredLocale.from_territory(self.territory) + all_locales = locales_from_territory.get_all_locales(self.preferences) + + # get preferred translation + for locale in all_locales: + if locale.language in self.translations: + self.preferred_translation = self.translations[locale.language] + break + + for locale in all_locales: + if locale.short_name in self.translations: + self.preferred_translation = self.translations[locale.short_name] + break + + self.preferred_locales = all_locales + + def select_translation(self, translation): + translation = self.translations[translation] + self.preferences.extend(self.all_preferences.get(translation.language, [])) + + # get locales from translation + locales_from_language = PreferredLocale.from_language(translation.short_name) + all_locales = locales_from_language.get_all_locales(self.preferences) + + # get preferred locale + for locale in all_locales: + if locale in self.preferred_locales: + self.preferred_locale = locale + break + else: + try: + self.preferred_locale = all_locales[0] + except IndexError: + self.preferred_locale = self.preferred_locales[0] + + # add the preferred locale to the beginning of locales + if self.preferred_locale in self.preferred_locales: + self.preferred_locales.remove(self.preferred_locale) + self.preferred_locales.insert(0, self.preferred_locale) + + # if territory is not set, use the one from preferred locale + self.territory = self.territory or self.preferred_locale.territory + + @staticmethod + def parse_langcode(langcode): + pattern = re.compile(r'(?P<language>[A-Za-z]+)(_(?P<territory>[A-Za-z]+))?(\.(?P<codeset>[-\w]+))?(@(?P<modifier>[-\w]+))?') + m = pattern.match(langcode) + return m.groupdict() + + @property + def install_lang_as_dict(self): + self.parse_langcode(self.install_lang) + + @property + def system_lang_as_dict(self): + self.parse_langcode(self.system_lang) + + def set_install_lang(self, langcode): + self.install_lang = langcode + + os.environ['LANG'] = langcode + os.environ['LC_NUMERIC'] = 'C' + + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error: + pass + + # XXX this is the sort of thing which you should never do, + # but we switch languages at runtime and thus need to invalidate + # the set of languages/mofiles which gettext knows about + gettext._translations = {} + + # XXX DEBUG + print 'set install lang to "%s"' % self.install_lang + + def set_system_lang(self, langcode): + self.system_lang = langcode + + # XXX DEBUG + print 'set system lang to "%s"' % self.system_lang diff --git a/pyanaconda/ui/gui/spokes/welcome.py b/pyanaconda/ui/gui/spokes/welcome.py index c5d0e1d..17ec719 100644 --- a/pyanaconda/ui/gui/spokes/welcome.py +++ b/pyanaconda/ui/gui/spokes/welcome.py @@ -23,6 +23,8 @@ from gi.repository import AnacondaWidgets, Gtk from pyanaconda.ui.gui.hubs.summary import SummaryHub from pyanaconda.ui.gui.spokes import StandaloneSpoke +from pyanaconda.localization import Language, LOCALE_PREFERENCES + __all__ = ["WelcomeLanguageSpoke"] class WelcomeLanguageSpoke(StandaloneSpoke): @@ -36,7 +38,10 @@ class WelcomeLanguageSpoke(StandaloneSpoke): selected = self.builder.get_object("languageViewSelection") (store, itr) = selected.get_selected() - self.data.lang.lang = store[itr][2] + lang = store[itr][2] + self.language.select_translation(lang) + + self.data.lang.lang = lang def populate(self): StandaloneSpoke.populate(self) @@ -47,26 +52,39 @@ class WelcomeLanguageSpoke(StandaloneSpoke): completion.set_text_column(1) store = self.builder.get_object("languageStore") - self._addLanguage(store, "English", "English", "en_US") - self._addLanguage(store, "Language A", "Language A", "C") - self._addLanguage(store, "Language B", "Language B", "C") - self._addLanguage(store, "Language C", "Language C", "C") - self._addLanguage(store, "Language D", "Language D", "C") - self._addLanguage(store, "Language E", "Language E", "C") - self._addLanguage(store, "Language F", "Language F", "C") - self._addLanguage(store, "Language G", "Language G", "C") - self._addLanguage(store, "Language H", "Language H", "C") - self._addLanguage(store, "Language I", "Language I", "C") - self._addLanguage(store, "Language J", "Language J", "C") - self._addLanguage(store, "Language K", "Language K", "C") + + # TODO We can use the territory from geoip here + # to preselect the translation, when it's available. + # Until then, use None. + territory = None + self.language = Language(LOCALE_PREFERENCES, territory=territory) + + # fill the list with available translations + for _code, trans in sorted(self.language.translations.items()): + self._addLanguage(store, trans.display_name, + trans.english_name, trans.short_name) + + # select the preferred translation + self._selectLanguage(store, self.language.preferred_translation.short_name) def setup(self): StandaloneSpoke.setup(self) - self.window.set_may_continue(False) + #self.window.set_may_continue(False) def _addLanguage(self, store, native, english, setting): store.append([native, english, setting]) + def _selectLanguage(self, store, language): + itr = store.get_iter_first() + while itr and store[itr][2] != language: + itr = store.iter_next(itr) + + treeview = self.builder.get_object("languageView") + selection = treeview.get_selection() + selection.select_iter(itr) + path = store.get_path(itr) + treeview.scroll_to_cell(path) + # Signal handlers. def clearLanguageEntry(self, entry, icon_pos, event): if icon_pos == Gtk.EntryIconPosition.SECONDARY: @@ -76,6 +94,12 @@ class WelcomeLanguageSpoke(StandaloneSpoke): (store, selected) = selection.get_selected_rows() self.window.set_may_continue(len(selected) > 0) + if selected: + lang = store[selected[0]][2] + self.language.set_install_lang(lang) + self.language.set_system_lang(lang) + # TODO reload the whole window so it gets translated + # Override the default in StandaloneSpoke so we can display the beta # warning dialog first. def _on_continue_clicked(self, cb): diff --git a/pyanaconda/ui/gui/spokes/welcome.ui b/pyanaconda/ui/gui/spokes/welcome.ui index b1894d5..d6cacd7 100644 --- a/pyanaconda/ui/gui/spokes/welcome.ui +++ b/pyanaconda/ui/gui/spokes/welcome.ui @@ -242,7 +242,9 @@ OS you can rely on. It's for testing purposes only.</property> <property name="headers_visible">False</property> <property name="search_column">0</property> <child internal-child="selection"> - <object class="GtkTreeSelection" id="treeview-selection"/> + <object class="GtkTreeSelection" id="treeview-selection"> + <signal name="changed" handler="on_selection_changed" swapped="no"/> + </object> </child> <child> <object class="GtkTreeViewColumn" id="nativeName"> -- 1.7.5.4 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list