These patches have one goal -- to avoid creating new instances of the XklWrapper class on language change because it is an expensive action. However, they also bring better code, more documentation, better "unittest test pontential" and altogether they are -2 lines of code.
They apply to David Shea's patches for keyboard layouts and switching options translations because I'd like to keep track of the changes in our git history and it would also allow us to easily revert changes made here if there are any problems within.
Vratislav Podzimek (4): Remove the Layout class and things we don't need in XklWrapper Move upcase_first_letter function to iutil Improve XklWrapper's API Translate layout and switching options descriptions on the fly
pyanaconda/iutil.py | 21 ++++++ pyanaconda/keyboard.py | 122 +++++++++++++++++------------------ pyanaconda/localization.py | 27 ++------ pyanaconda/ui/gui/spokes/keyboard.py | 18 +++--- 4 files changed, 93 insertions(+), 95 deletions(-)
We no longer use libxklavier to get language/country-default keyboard layout so we don't need to hold all items that were used for that.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/keyboard.py | 33 --------------------------------- 1 file changed, 33 deletions(-)
diff --git a/pyanaconda/keyboard.py b/pyanaconda/keyboard.py index fa10579..4967cb4 100644 --- a/pyanaconda/keyboard.py +++ b/pyanaconda/keyboard.py @@ -338,24 +338,6 @@ def item_str(s):
return s.decode("utf-8") #there are some non-ascii layout descriptions
-class _Layout(object): - """Internal class representing a single layout variant""" - - def __init__(self, name, desc): - self.name = name - self.desc = desc - - def __str__(self): - return '%s (%s)' % (self.name, self.desc) - - def __eq__(self, obj): - return isinstance(obj, self.__class__) and \ - self.name == obj.name - - @property - def description(self): - return self.desc - class XklWrapperError(KeyboardConfigError): """Exception class for reporting libxklavier-related problems"""
@@ -424,10 +406,7 @@ class XklWrapper(object): self.configreg = Xkl.ConfigRegistry.get_instance(self._engine) self.configreg.load(False)
- self._language_keyboard_variants = dict() - self._country_keyboard_variants = dict() self._switching_options = list() - self._variants_list = list()
#we want to display layouts as 'language (description)' self.name_to_show_str = dict() @@ -460,8 +439,6 @@ class XklWrapper(object): else: self.name_to_show_str[name] = "%s" % description.encode("utf-8")
- self._variants_list.append(_Layout(name, description)) - def _get_country_variant(self, c_reg, item, subitem, country): if subitem: name = item_str(item.name) + " (" + item_str(subitem.name) + ")" @@ -478,27 +455,17 @@ class XklWrapper(object): else: self.name_to_show_str[name] = "%s" % description.encode("utf-8")
- self._variants_list.append(_Layout(name, description)) - def _get_language_variants(self, c_reg, item, user_data=None): - #helper "global" variable - self._variants_list = list() lang_name, lang_desc = item_str(item.name), item_str(item.description)
c_reg.foreach_language_variant(lang_name, self._get_lang_variant, lang_desc)
- self._language_keyboard_variants[lang_desc] = self._variants_list - def _get_country_variants(self, c_reg, item, user_data=None): - #helper "global" variable - self._variants_list = list() country_name, country_desc = item_str(item.name), item_str(item.description)
c_reg.foreach_country_variant(country_name, self._get_country_variant, country_desc)
- self._country_keyboard_variants[country_name] = self._variants_list - def _get_switch_option(self, c_reg, item, user_data=None): """Helper function storing layout switching options in foreach cycle""" desc = item_str(item.description)
This function may come handy in various module, not only in the localization module.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/iutil.py | 21 +++++++++++++++++++++ pyanaconda/localization.py | 27 ++++----------------------- 2 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py index 4e362ff..020f15d 100644 --- a/pyanaconda/iutil.py +++ b/pyanaconda/iutil.py @@ -742,3 +742,24 @@ def lowerASCII(s): locale-independent. """ return string.translate(_toASCII(s), _ASCIIlower_table) + +def upcase_first_letter(string): + """ + Helper function that upcases the first letter of the string. Python's + standard string.capitalize() not only upcases the first letter but also + lowercases all the others. string.title() capitalizes all words in the + string. + + :type string: either a str or unicode object + :return: the given string with the first letter upcased + :rtype: str or unicode (depends on the input) + + """ + + if not string: + # cannot change anything + return string + elif len(string) == 1: + return string.upper() + else: + return string[0].upper() + string[1:] diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py index f9469d8..7e473dd 100644 --- a/pyanaconda/localization.py +++ b/pyanaconda/localization.py @@ -26,6 +26,8 @@ import re import langtable import glob
+from pyanaconda.iutil import upcase_first_letter + import logging log = logging.getLogger("anaconda")
@@ -47,27 +49,6 @@ class InvalidLocaleSpec(LocalizationConfigError):
pass
-def _upcase_first_letter(string): - """ - Helper function that upcases the first letter of the string. Python's - standard string.capitalize() not only upcases the first letter but also - lowercases all the others. string.title() capitalizes all words in the - string. - - :type string: either a str or unicode object - :return: the given string with the first letter upcased - :rtype: str or unicode (depends on the input) - - """ - - if not string: - # cannot change anything - return string - elif len(string) == 1: - return string.upper() - else: - return string[0].upper() + string[1:] - def parse_langcode(langcode): """ For a given langcode (e.g. 'SR_RS.UTF-8@latin') returns a dictionary @@ -237,7 +218,7 @@ def get_english_name(locale): scriptId=parts.get("script", ""), languageIdQuery="en")
- return _upcase_first_letter(name) + return upcase_first_letter(name)
def get_native_name(locale): """ @@ -261,7 +242,7 @@ def get_native_name(locale): languageIdQuery=parts["language"], scriptIdQuery=parts.get("script", ""))
- return _upcase_first_letter(name) + return upcase_first_letter(name)
def get_available_translations(localedir=None): """
On 09/13/2013 06:59 AM, Vratislav Podzimek wrote:
This function may come handy in various module, not only in the localization module.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
pyanaconda/iutil.py | 21 +++++++++++++++++++++ pyanaconda/localization.py | 27 ++++----------------------- 2 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py index 4e362ff..020f15d 100644 --- a/pyanaconda/iutil.py +++ b/pyanaconda/iutil.py @@ -742,3 +742,24 @@ def lowerASCII(s): locale-independent. """ return string.translate(_toASCII(s), _ASCIIlower_table)
+def upcase_first_letter(string):
- """
- Helper function that upcases the first letter of the string. Python's
- standard string.capitalize() not only upcases the first letter but also
- lowercases all the others. string.title() capitalizes all words in the
- string.
- :type string: either a str or unicode object
- :return: the given string with the first letter upcased
- :rtype: str or unicode (depends on the input)
- """
- if not string:
# cannot change anythingreturn string- elif len(string) == 1:
return string.upper()- else:
return string[0].upper() + string[1:]diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py index f9469d8..7e473dd 100644 --- a/pyanaconda/localization.py +++ b/pyanaconda/localization.py @@ -26,6 +26,8 @@ import re import langtable import glob
+from pyanaconda.iutil import upcase_first_letter
- import logging log = logging.getLogger("anaconda")
@@ -47,27 +49,6 @@ class InvalidLocaleSpec(LocalizationConfigError):
pass-def _upcase_first_letter(string):
- """
- Helper function that upcases the first letter of the string. Python's
- standard string.capitalize() not only upcases the first letter but also
- lowercases all the others. string.title() capitalizes all words in the
- string.
- :type string: either a str or unicode object
- :return: the given string with the first letter upcased
- :rtype: str or unicode (depends on the input)
- """
- if not string:
# cannot change anythingreturn string- elif len(string) == 1:
return string.upper()- else:
return string[0].upper() + string[1:]- def parse_langcode(langcode): """ For a given langcode (e.g. 'SR_RS.UTF-8@latin') returns a dictionary
@@ -237,7 +218,7 @@ def get_english_name(locale): scriptId=parts.get("script", ""), languageIdQuery="en")
- return _upcase_first_letter(name)
return upcase_first_letter(name)
def get_native_name(locale): """
@@ -261,7 +242,7 @@ def get_native_name(locale): languageIdQuery=parts["language"], scriptIdQuery=parts.get("script", ""))
- return _upcase_first_letter(name)
return upcase_first_letter(name)
def get_available_translations(localedir=None): """
"string" isn't the best parameter name, since it conflicts with the string module, which is imported in iutil. Also need to change the use of _upcase_first_letter in tests/pyanaconda/localization_test.py
On Fri, 2013-09-13 at 09:05 -0400, David Shea wrote:
On 09/13/2013 06:59 AM, Vratislav Podzimek wrote:
This function may come handy in various module, not only in the localization module.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
pyanaconda/iutil.py | 21 +++++++++++++++++++++ pyanaconda/localization.py | 27 ++++----------------------- 2 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py index 4e362ff..020f15d 100644 --- a/pyanaconda/iutil.py +++ b/pyanaconda/iutil.py @@ -742,3 +742,24 @@ def lowerASCII(s): locale-independent. """ return string.translate(_toASCII(s), _ASCIIlower_table)
+def upcase_first_letter(string):
- """
- Helper function that upcases the first letter of the string. Python's
- standard string.capitalize() not only upcases the first letter but also
- lowercases all the others. string.title() capitalizes all words in the
- string.
- :type string: either a str or unicode object
- :return: the given string with the first letter upcased
- :rtype: str or unicode (depends on the input)
- """
- if not string:
# cannot change anythingreturn string- elif len(string) == 1:
return string.upper()- else:
return string[0].upper() + string[1:]diff --git a/pyanaconda/localization.py b/pyanaconda/localization.py index f9469d8..7e473dd 100644 --- a/pyanaconda/localization.py +++ b/pyanaconda/localization.py @@ -26,6 +26,8 @@ import re import langtable import glob
+from pyanaconda.iutil import upcase_first_letter
- import logging log = logging.getLogger("anaconda")
@@ -47,27 +49,6 @@ class InvalidLocaleSpec(LocalizationConfigError):
pass-def _upcase_first_letter(string):
- """
- Helper function that upcases the first letter of the string. Python's
- standard string.capitalize() not only upcases the first letter but also
- lowercases all the others. string.title() capitalizes all words in the
- string.
- :type string: either a str or unicode object
- :return: the given string with the first letter upcased
- :rtype: str or unicode (depends on the input)
- """
- if not string:
# cannot change anythingreturn string- elif len(string) == 1:
return string.upper()- else:
return string[0].upper() + string[1:]- def parse_langcode(langcode): """ For a given langcode (e.g. 'SR_RS.UTF-8@latin') returns a dictionary
@@ -237,7 +218,7 @@ def get_english_name(locale): scriptId=parts.get("script", ""), languageIdQuery="en")
- return _upcase_first_letter(name)
return upcase_first_letter(name)
def get_native_name(locale): """
@@ -261,7 +242,7 @@ def get_native_name(locale): languageIdQuery=parts["language"], scriptIdQuery=parts.get("script", ""))
- return _upcase_first_letter(name)
return upcase_first_letter(name)
def get_available_translations(localedir=None): """
"string" isn't the best parameter name, since it conflicts with the string module, which is imported in iutil. Also need to change the use of _upcase_first_letter in tests/pyanaconda/localization_test.py
Good points, thanks. Maybe I should upgrade my system to be able to run 'make check' and use it. :) Fixing locally.
Instead of exposing internal structures, the XklWrapper class should provide methods to get descriptions for layout-variants and switching options.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/keyboard.py | 76 ++++++++++++++++++++++++------------ pyanaconda/ui/gui/spokes/keyboard.py | 18 ++++----- 2 files changed, 60 insertions(+), 34 deletions(-)
diff --git a/pyanaconda/keyboard.py b/pyanaconda/keyboard.py index 4967cb4..1f6cb5a 100644 --- a/pyanaconda/keyboard.py +++ b/pyanaconda/keyboard.py @@ -38,6 +38,8 @@ import re import shutil import ctypes
+from collections import namedtuple + from pyanaconda import iutil from pyanaconda import flags from pyanaconda.safe_dbus import dbus_call_safe_sync, dbus_get_property_safe_sync @@ -59,6 +61,9 @@ LAYOUT_VARIANT_RE = re.compile(r'^\s*(\w+)\s*' # layout plus r'(?:(?:(\s*(\w+)\s*))' # variant in parentheses r'|(?:$))\s*') # or nothing
+# namedtuple for information about a keyboard layout (its language and description) +LayoutInfo = namedtuple("LayoutInfo", ["lang", "desc"]) + class KeyboardConfigError(Exception): """Exception class for keyboard configuration related problems"""
@@ -406,14 +411,8 @@ class XklWrapper(object): self.configreg = Xkl.ConfigRegistry.get_instance(self._engine) self.configreg.load(False)
- self._switching_options = list() - - #we want to display layouts as 'language (description)' - self.name_to_show_str = dict() - - #we want to display layout switching options as e.g. "Alt + Shift" not - #as "grp:alt_shift_toggle" - self.switch_to_show_str = dict() + self._layout_infos = dict() + self._switch_opt_infos = dict()
#this might take quite a long time self.configreg.foreach_language(self._get_language_variants, None) @@ -432,12 +431,9 @@ class XklWrapper(object):
#if this layout has already been added for some other language, #do not add it again (would result in duplicates in our lists) - if name not in self.name_to_show_str: - if lang: - self.name_to_show_str[name] = "%s (%s)" % (lang.encode("utf-8"), - description.encode("utf-8")) - else: - self.name_to_show_str[name] = "%s" % description.encode("utf-8") + if name not in self._layout_infos: + self._layout_infos[name] = LayoutInfo(lang.encode("utf-8"), + description.encode("utf-8"))
def _get_country_variant(self, c_reg, item, subitem, country): if subitem: @@ -448,12 +444,9 @@ class XklWrapper(object): description = item_str(item.description)
# if the layout was not added with any language, add it with a country - if name not in self.name_to_show_str: - if country: - self.name_to_show_str[name] = "%s (%s)" % (country.encode("utf-8"), - description.encode("utf-8")) - else: - self.name_to_show_str[name] = "%s" % description.encode("utf-8") + if name not in self._layout_infos: + self._layout_infos[name] = LayoutInfo(country.encode("utf-8"), + description.encode("utf-8"))
def _get_language_variants(self, c_reg, item, user_data=None): lang_name, lang_desc = item_str(item.name), item_str(item.description) @@ -471,8 +464,7 @@ class XklWrapper(object): desc = item_str(item.description) name = item_str(item.name)
- self._switching_options.append(name) - self.switch_to_show_str[name] = desc.encode("utf-8") + self._switch_opt_infos[name] = desc.encode("utf-8")
def get_current_layout(self): """ @@ -503,12 +495,46 @@ class XklWrapper(object): def get_available_layouts(self): """A generator yielding layouts (no need to store them as a bunch)"""
- return self.name_to_show_str.iterkeys() + return self._layout_infos.iterkeys()
def get_switching_options(self): """Method returning list of available layout switching options"""
- return self._switching_options + return self._switch_opt_infos.iterkeys() + + def get_layout_variant_description(self, layout_variant, with_lang=True): + """ + Get description of the given layout-variant. + + :param layout_variant: layout-variant specification (e.g. 'cz (qwerty)') + :type layout_variant: str + :param with_lang: whether to include language of the layout-variant (if defined) + in the description or not + :type with_lang: bool + :return: description of the layout-variant specification (e.g. 'Czech (qwerty)') + :rtype: str + + """ + + layout_info = self._layout_infos[layout_variant] + if with_lang and layout_info.lang: + return "%s (%s)" % (iutil.upcase_first_letter(layout_info.lang), + layout_info.desc) + else: + return layout_info.desc + + def get_switch_opt_description(self, switch_opt): + """ + Get description of the given layout switching option. + + :param switch_opt: switching option name/ID (e.g. 'grp:alt_shift_toggle') + :type switch_opt: str + :return: description of the layout switching option (e.g. 'Alt + Shift') + :rtype: str + + """ + + return self._switch_opt_infos[switch_opt]
def activate_default_layout(self): """ @@ -522,7 +548,7 @@ class XklWrapper(object): def is_valid_layout(self, layout): """Return if given layout is valid layout or not"""
- return layout in self.name_to_show_str + return layout in self._layout_infos
def add_layout(self, layout): """ diff --git a/pyanaconda/ui/gui/spokes/keyboard.py b/pyanaconda/ui/gui/spokes/keyboard.py index 0cc8e48..bbc59d3 100644 --- a/pyanaconda/ui/gui/spokes/keyboard.py +++ b/pyanaconda/ui/gui/spokes/keyboard.py @@ -39,11 +39,11 @@ __all__ = ["KeyboardSpoke"] LAYOUT_SWITCHING_INFO = N_("%s to switch layouts.")
def _show_layout(column, renderer, model, itr, wrapper): - value = wrapper.name_to_show_str[model[itr][0]] + value = wrapper.get_layout_variant_description(model[itr][0]) renderer.set_property("text", value)
def _show_description(column, renderer, model, itr, wrapper): - value = wrapper.switch_to_show_str[model[itr][0]] + value = wrapper.get_switch_opt_description(model[itr][0]) if model[itr][1]: value = "<b>%s</b>" % value renderer.set_property("markup", value) @@ -61,7 +61,7 @@ class AddLayoutDialog(GUIObject):
def matches_entry(self, model, itr, user_data=None): value = model[itr][0] - value = self._xkl_wrapper.name_to_show_str[value] + value = self._xkl_wrapper.get_layout_variant_description(value) entry_text = self._entry.get_text() if entry_text is not None: entry_text = entry_text.lower() @@ -87,8 +87,8 @@ class AddLayoutDialog(GUIObject):
value1 = model[itr1][0] value2 = model[itr2][0] - show_str1 = self._xkl_wrapper.name_to_show_str[value1] - show_str2 = self._xkl_wrapper.name_to_show_str[value2] + show_str1 = self._xkl_wrapper.get_layout_variant_description(value1) + show_str2 = self._xkl_wrapper.get_layout_variant_description(value2)
if show_str1 < show_str2: return -1 @@ -223,8 +223,8 @@ class ConfigureSwitchingDialog(GUIObject):
value1 = model[itr1][0] value2 = model[itr2][0] - show_str1 = self._xkl_wrapper.switch_to_show_str[value1] - show_str2 = self._xkl_wrapper.switch_to_show_str[value2] + show_str1 = self._xkl_wrapper.get_switch_opt_description(value1) + show_str2 = self._xkl_wrapper.get_switch_opt_description(value2)
if show_str1 < show_str2: return -1 @@ -297,7 +297,7 @@ class KeyboardSpoke(NormalSpoke): @property def status(self): # We don't need to check that self._store is empty, because that isn't allowed. - return self._xkl_wrapper.name_to_show_str[self._store[0][0]] + return self._xkl_wrapper.get_layout_variant_description(self._store[0][0])
def initialize(self): NormalSpoke.initialize(self) @@ -379,7 +379,7 @@ class KeyboardSpoke(NormalSpoke): def _refresh_switching_info(self): if self.data.keyboard.switch_options: first_option = self.data.keyboard.switch_options[0] - desc = self._xkl_wrapper.switch_to_show_str[first_option] + desc = self._xkl_wrapper.get_switch_opt_description(first_option)
self._layoutSwitchLabel.set_text(_(LAYOUT_SWITCHING_INFO) % desc) else:
Creating a new instance of the XklWrapper is quite an expensive action as it reads and processes a lot of data and creates a lot of internal structure. So instead of creating new instance the get fresh translations on language change, it's better to translate strings on the fly if they are requested.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/keyboard.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/pyanaconda/keyboard.py b/pyanaconda/keyboard.py index 1f6cb5a..945c52b 100644 --- a/pyanaconda/keyboard.py +++ b/pyanaconda/keyboard.py @@ -37,6 +37,7 @@ import os import re import shutil import ctypes +import gettext
from collections import namedtuple
@@ -64,6 +65,9 @@ LAYOUT_VARIANT_RE = re.compile(r'^\s*(\w+)\s*' # layout plus # namedtuple for information about a keyboard layout (its language and description) LayoutInfo = namedtuple("LayoutInfo", ["lang", "desc"])
+Xkb_ = lambda x: gettext.ldgettext("xkeyboard-config", x) +iso_ = lambda x: gettext.ldgettext("iso_639", x) + class KeyboardConfigError(Exception): """Exception class for keyboard configuration related problems"""
@@ -363,13 +367,8 @@ class XklWrapper(object):
@staticmethod def get_instance(): - # If the language has changed, we need to grab new strings - if os.environ["LANG"] != XklWrapper._instance_lang: - XklWrapper._instance = None - if not XklWrapper._instance: XklWrapper._instance = XklWrapper() - XklWrapper._instance_lang = os.environ["LANG"]
return XklWrapper._instance
@@ -518,8 +517,11 @@ class XklWrapper(object):
layout_info = self._layout_infos[layout_variant] if with_lang and layout_info.lang: - return "%s (%s)" % (iutil.upcase_first_letter(layout_info.lang), - layout_info.desc) + # translate language and upcase its first letter, translate the + # layout-variant description + xlated_lang = iso_(layout_info.lang) + return "%s (%s)" % (iutil.upcase_first_letter(xlated_lang.decode("utf-8")), + Xkb_(layout_info.desc)) else: return layout_info.desc
@@ -534,7 +536,8 @@ class XklWrapper(object):
"""
- return self._switch_opt_infos[switch_opt] + # translate the description of the switching option + return Xkb_(self._switch_opt_infos[switch_opt])
def activate_default_layout(self): """
On Fri, 2013-09-13 at 12:59 +0200, Vratislav Podzimek wrote:
Creating a new instance of the XklWrapper is quite an expensive action as it reads and processes a lot of data and creates a lot of internal structure. So instead of creating new instance the get fresh translations on language change, it's better to translate strings on the fly if they are requested.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
pyanaconda/keyboard.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/pyanaconda/keyboard.py b/pyanaconda/keyboard.py index 1f6cb5a..945c52b 100644 --- a/pyanaconda/keyboard.py +++ b/pyanaconda/keyboard.py @@ -37,6 +37,7 @@ import os import re import shutil import ctypes +import gettext
from collections import namedtuple
@@ -64,6 +65,9 @@ LAYOUT_VARIANT_RE = re.compile(r'^\s*(\w+)\s*' # layout plus # namedtuple for information about a keyboard layout (its language and description) LayoutInfo = namedtuple("LayoutInfo", ["lang", "desc"])
+Xkb_ = lambda x: gettext.ldgettext("xkeyboard-config", x) +iso_ = lambda x: gettext.ldgettext("iso_639", x)
class KeyboardConfigError(Exception): """Exception class for keyboard configuration related problems"""
@@ -363,13 +367,8 @@ class XklWrapper(object):
@staticmethod def get_instance():
# If the language has changed, we need to grab new stringsif os.environ["LANG"] != XklWrapper._instance_lang:
The _instance_lang class attribute should be removed completely from the class. Fixed locally.
On 09/13/2013 06:59 AM, Vratislav Podzimek wrote:
These patches have one goal -- to avoid creating new instances of the XklWrapper class on language change because it is an expensive action. However, they also bring better code, more documentation, better "unittest test pontential" and altogether they are -2 lines of code.
They apply to David Shea's patches for keyboard layouts and switching options translations because I'd like to keep track of the changes in our git history and it would also allow us to easily revert changes made here if there are any problems within.
Vratislav Podzimek (4): Remove the Layout class and things we don't need in XklWrapper Move upcase_first_letter function to iutil Improve XklWrapper's API Translate layout and switching options descriptions on the fly
pyanaconda/iutil.py | 21 ++++++ pyanaconda/keyboard.py | 122 +++++++++++++++++------------------ pyanaconda/localization.py | 27 ++------ pyanaconda/ui/gui/spokes/keyboard.py | 18 +++--- 4 files changed, 93 insertions(+), 95 deletions(-)
Besides my comment and your comment about _instance_lang, these look good to me
anaconda-patches@lists.fedorahosted.org