What happens is that we have the following translatable strings: - "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]" - "q" - "c" - "r" All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com --- pyanaconda/ui/tui/hubs/__init__.py | 6 ++++-- pyanaconda/ui/tui/hubs/summary.py | 5 ++++- pyanaconda/ui/tui/simpleline/base.py | 4 +++- pyanaconda/ui/tui/spokes/langsupport.py | 4 +++- pyanaconda/ui/tui/spokes/time_spoke.py | 3 ++- pyanaconda/ui/tui/tuiobject.py | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/pyanaconda/ui/tui/hubs/__init__.py b/pyanaconda/ui/tui/hubs/__init__.py index 82b49fe..69d7985 100644 --- a/pyanaconda/ui/tui/hubs/__init__.py +++ b/pyanaconda/ui/tui/hubs/__init__.py @@ -129,7 +129,9 @@ class TUIHub(TUIObject, common.Hub): """ if self._spoke_count == 1: single_spoke_title = self._spokes.values()[0].title - return _(u" Please make your choice from [ '1' to enter the %s spoke | 'q' to quit |\n" - " 'c' to continue | 'r' to refresh]: ") % single_spoke_title + # TRANSLATORS: 'q' to quit, 'c' to continue, 'r' to refresh + return _(u" Please make your choice from [ '1' to enter the %s spoke | '%s' to quit |\n" + " '%s' to continue | '%s' to refresh]: ") % (single_spoke_title, _("q"), _("c"), _("r")) + else: return super(TUIHub, self).prompt(args) diff --git a/pyanaconda/ui/tui/hubs/summary.py b/pyanaconda/ui/tui/hubs/summary.py index 5d414ee..d6fdcaf 100644 --- a/pyanaconda/ui/tui/hubs/summary.py +++ b/pyanaconda/ui/tui/hubs/summary.py @@ -87,7 +87,10 @@ class SummaryHub(TUIHub):
# override the default prompt since we want to offer the 'b' to begin # installation option here - return _(" Please make your choice from above ['q' to quit | 'b' to begin installation |\n 'r' to refresh]: ") + + # TRANSLATORS: 'q' to quit, 'b' to begin installation, 'r' to refresh + return _(" Please make your choice from above ['%s' to quit | '%s' to begin installation |\n '%s' to refresh]: ") % \ + (_("q"), _("b"), _("r"))
def input(self, args, key): """Handle user input. Numbers are used to show a spoke, the rest is passed diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index c4fe736..d75a893 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -627,7 +627,9 @@ class UIScreen(object): to skip further input processing :rtype: unicode|None """ - return _(u" Please make your choice from above ['q' to quit | 'c' to continue |\n 'r' to refresh]: ") + # TRANSLATORS: 'q' to quit, "c" to continue, "r" to refresh + return _(u" Please make your choice from above ['%s' to quit | '%s' to continue |\n '%s' to refresh]: ") % \ + (_("q"), _("c"), _("r"))
@property def app(self): diff --git a/pyanaconda/ui/tui/spokes/langsupport.py b/pyanaconda/ui/tui/spokes/langsupport.py index 0b1e836..0baf330 100644 --- a/pyanaconda/ui/tui/spokes/langsupport.py +++ b/pyanaconda/ui/tui/spokes/langsupport.py @@ -117,7 +117,9 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
def prompt(self, args=None): """ Override default prompt with a custom prompt. """ - return _("Please select language support to install.\n[b to language list, c to continue, q to quit]: ") + # TRANSLATORS: 'b' to go back, "c" to continue, "q" to quit + return _("Please select language support to install.\n[%s to return to language list, %s to continue, %s to quit]: ") % \ + (_("b"), _("c"), _("q"))
def apply(self): """ Store the selected langsupport locales """ diff --git a/pyanaconda/ui/tui/spokes/time_spoke.py b/pyanaconda/ui/tui/spokes/time_spoke.py index a6e59e3..a8dfdac 100644 --- a/pyanaconda/ui/tui/spokes/time_spoke.py +++ b/pyanaconda/ui/tui/spokes/time_spoke.py @@ -131,7 +131,8 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): return INPUT_PROCESSED
def prompt(self, args = None): - return _("Please select the timezone.\nUse numbers or type names directly [b to region list, q to quit]: ") + # TRANSLATORS: 'b' to go back, "q" to quit + return _("Please select the timezone.\nUse numbers or type names directly [%s to region list, %s to quit]: ") % (_("b"), _("q"))
def apply(self): self.data.timezone.timezone = self._selection diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 9fbb726..58d3664 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -128,7 +128,7 @@ class YesNoDialog(tui.UIScreen): return True
def prompt(self, args = None): - return _("Please respond 'yes' or 'no': ") + return _("Please respond '%s' or '%s': ") % (_("yes"), _("no"))
def input(self, args, key): if key == _("yes"):
On Fri, Jul 24, 2015 at 02:06:07PM +0200, Martin Kolman wrote:
What happens is that we have the following translatable strings:
- "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]"
- "q"
- "c"
- "r"
All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com
pyanaconda/ui/tui/hubs/__init__.py | 6 ++++-- pyanaconda/ui/tui/hubs/summary.py | 5 ++++- pyanaconda/ui/tui/simpleline/base.py | 4 +++- pyanaconda/ui/tui/spokes/langsupport.py | 4 +++- pyanaconda/ui/tui/spokes/time_spoke.py | 3 ++- pyanaconda/ui/tui/tuiobject.py | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 9fbb726..58d3664 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -128,7 +128,7 @@ class YesNoDialog(tui.UIScreen): return True
def prompt(self, args = None):
return _("Please respond 'yes' or 'no': ")
return _("Please respond '%s' or '%s': ") % (_("yes"), _("no"))
Does this also need a translator comment?
I like this patch, barring problems from the translation team it has my ack.
On 07/24/2015 08:06 AM, Martin Kolman wrote:
What happens is that we have the following translatable strings:
- "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]"
- "q"
- "c"
- "r"
All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com
pyanaconda/ui/tui/hubs/__init__.py | 6 ++++-- pyanaconda/ui/tui/hubs/summary.py | 5 ++++- pyanaconda/ui/tui/simpleline/base.py | 4 +++- pyanaconda/ui/tui/spokes/langsupport.py | 4 +++- pyanaconda/ui/tui/spokes/time_spoke.py | 3 ++- pyanaconda/ui/tui/tuiobject.py | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/pyanaconda/ui/tui/hubs/__init__.py b/pyanaconda/ui/tui/hubs/__init__.py index 82b49fe..69d7985 100644 --- a/pyanaconda/ui/tui/hubs/__init__.py +++ b/pyanaconda/ui/tui/hubs/__init__.py @@ -129,7 +129,9 @@ class TUIHub(TUIObject, common.Hub): """ if self._spoke_count == 1: single_spoke_title = self._spokes.values()[0].title
return _(u" Please make your choice from [ '1' to enter the %s spoke | 'q' to quit |\n"
" 'c' to continue | 'r' to refresh]: ") % single_spoke_title
# TRANSLATORS: 'q' to quit, 'c' to continue, 'r' to refresh
return _(u" Please make your choice from [ '1' to enter the %s spoke | '%s' to quit |\n"
" '%s' to continue | '%s' to refresh]: ") % (single_spoke_title, _("q"), _("c"), _("r"))
The translators comment is going to be associated with the "Please make your choice..." string instead of the three strings for the individual letters. It'll look kind of ugly, but each of _() calls for q, c, and r needs to go on its own line, with a TRANSLATORS comment above it.
Also, please use a context for all of the single letter translations. Similar to how its used to potentially give different translated keyboard accelerators to the same word in the GUI, it needs to be used in the TUI to avoid things like this that I just found:
#. TRANSLATORS: 'b' to begin installation #. TRANSLATORS: 'b' to go back #: pyanaconda/ui/tui/hubs/summary.py:93 pyanaconda/ui/tui/hubs/summary.py:107 #: pyanaconda/ui/tui/spokes/langsupport.py:112 #: pyanaconda/ui/tui/spokes/langsupport.py:122 #: pyanaconda/ui/tui/spokes/time_spoke.py:135 msgid "b" msgstr ""
Oops. I can probably add a test for that.
Anyway the context here should be "TUI|Spoke Navigation".
else: return super(TUIHub, self).prompt(args)
diff --git a/pyanaconda/ui/tui/hubs/summary.py b/pyanaconda/ui/tui/hubs/summary.py index 5d414ee..d6fdcaf 100644 --- a/pyanaconda/ui/tui/hubs/summary.py +++ b/pyanaconda/ui/tui/hubs/summary.py @@ -87,7 +87,10 @@ class SummaryHub(TUIHub):
# override the default prompt since we want to offer the 'b' to begin # installation option here
return _(" Please make your choice from above ['q' to quit | 'b' to begin installation |\n 'r' to refresh]: ")
# TRANSLATORS: 'q' to quit, 'b' to begin installation, 'r' to refresh
return _(" Please make your choice from above ['%s' to quit | '%s' to begin installation |\n '%s' to refresh]: ") % \
(_("q"), _("b"), _("r"))
Same thing with the comment and contexts. Master uses "TUI Spoke Navigation" for here as well, and it looks like it's missing on rhel7 for the _('b') and _('c') translations also in this file.
def input(self, args, key): """Handle user input. Numbers are used to show a spoke, the rest is passed
diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index c4fe736..d75a893 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -627,7 +627,9 @@ class UIScreen(object): to skip further input processing :rtype: unicode|None """
return _(u" Please make your choice from above ['q' to quit | 'c' to continue |\n 'r' to refresh]: ")
# TRANSLATORS: 'q' to quit, "c" to continue, "r" to refresh
return _(u" Please make your choice from above ['%s' to quit | '%s' to continue |\n '%s' to refresh]: ") % \
(_("q"), _("c"), _("r"))
Same.
@property def app(self):
diff --git a/pyanaconda/ui/tui/spokes/langsupport.py b/pyanaconda/ui/tui/spokes/langsupport.py index 0b1e836..0baf330 100644 --- a/pyanaconda/ui/tui/spokes/langsupport.py +++ b/pyanaconda/ui/tui/spokes/langsupport.py @@ -117,7 +117,9 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
def prompt(self, args=None): """ Override default prompt with a custom prompt. """
return _("Please select language support to install.\n[b to language list, c to continue, q to quit]: ")
# TRANSLATORS: 'b' to go back, "c" to continue, "q" to quit
return _("Please select language support to install.\n[%s to return to language list, %s to continue, %s to quit]: ") % \
(_("b"), _("c"), _("q"))
Same, master has the context "TUI Spoke Navigation|Language Support"
def apply(self): """ Store the selected langsupport locales """
diff --git a/pyanaconda/ui/tui/spokes/time_spoke.py b/pyanaconda/ui/tui/spokes/time_spoke.py index a6e59e3..a8dfdac 100644 --- a/pyanaconda/ui/tui/spokes/time_spoke.py +++ b/pyanaconda/ui/tui/spokes/time_spoke.py @@ -131,7 +131,8 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): return INPUT_PROCESSED
def prompt(self, args = None):
return _("Please select the timezone.\nUse numbers or type names directly [b to region list, q to quit]: ")
# TRANSLATORS: 'b' to go back, "q" to quit
return _("Please select the timezone.\nUse numbers or type names directly [%s to region list, %s to quit]: ") % (_("b"), _("q"))
The actual use of "b" is not translated (also true on master), turns out. Same thing with comments and contexts.
def apply(self): self.data.timezone.timezone = self._selection
diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 9fbb726..58d3664 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -128,7 +128,7 @@ class YesNoDialog(tui.UIScreen): return True
def prompt(self, args = None):
return _("Please respond 'yes' or 'no': ")
return _("Please respond '%s' or '%s': ") % (_("yes"), _("no"))
This needs to use named parameters (%(yes), %(no))
def input(self, args, key): if key == _("yes"):
Changes from v1: * make sure each individual keystroke translation gets its own TRANSLATORS: comment line, so that it gets a properly comment in the pot file * use proper translation contexts for the keystrokes * translate an untranslated keystroke
Martin Kolman (1): Prevent incomplete translations from making the TUI unusable (#1235617)
pyanaconda/ui/tui/hubs/__init__.py | 11 +++++++++-- pyanaconda/ui/tui/hubs/summary.py | 16 ++++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 11 ++++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 15 ++++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 63 insertions(+), 17 deletions(-)
What happens is that we have the following translatable strings: - "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]" - "q" - "c" - "r" All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately, simplified example:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com --- pyanaconda/ui/tui/hubs/__init__.py | 11 +++++++++-- pyanaconda/ui/tui/hubs/summary.py | 16 ++++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 11 ++++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 15 ++++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 63 insertions(+), 17 deletions(-)
diff --git a/pyanaconda/ui/tui/hubs/__init__.py b/pyanaconda/ui/tui/hubs/__init__.py index 82b49fe..b67ed0b 100644 --- a/pyanaconda/ui/tui/hubs/__init__.py +++ b/pyanaconda/ui/tui/hubs/__init__.py @@ -129,7 +129,14 @@ class TUIHub(TUIObject, common.Hub): """ if self._spoke_count == 1: single_spoke_title = self._spokes.values()[0].title - return _(u" Please make your choice from [ '1' to enter the %s spoke | 'q' to quit |\n" - " 'c' to continue | 'r' to refresh]: ") % single_spoke_title + # TRANSLATORS: 'q' to quit + quit = C_('TUI|Spoke Navigation', 'q'), + # TRANSLATORS:'c' to continue + forward = C_('TUI|Spoke Navigation', 'c'), + # TRANSLATORS:'r' to refresh + refresh = C_('TUI|Spoke Navigation', 'r') + + return _(u" Please make your choice from [ '1' to enter the %s spoke | '%s' to quit |\n" + " '%s' to continue | '%s' to refresh]: ") % (single_spoke_title, quit, forward, refresh) else: return super(TUIHub, self).prompt(args) diff --git a/pyanaconda/ui/tui/hubs/summary.py b/pyanaconda/ui/tui/hubs/summary.py index 5d414ee..866794a 100644 --- a/pyanaconda/ui/tui/hubs/summary.py +++ b/pyanaconda/ui/tui/hubs/summary.py @@ -24,7 +24,7 @@ from pyanaconda.ui.lib.space import FileSystemSpaceChecker, DirInstallSpaceCheck from pyanaconda.ui.tui.hubs import TUIHub from pyanaconda.flags import flags from pyanaconda.errors import CmdlineError -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ import sys import time
@@ -87,8 +87,16 @@ class SummaryHub(TUIHub):
# override the default prompt since we want to offer the 'b' to begin # installation option here - return _(" Please make your choice from above ['q' to quit | 'b' to begin installation |\n 'r' to refresh]: ")
+ # TRANSLATORS: 'q' to quit + quit = C_('TUI|Spoke Navigation', 'q') + # TRANSLATORS: 'b' to begin installation + begin = C_('TUI|Spoke Navigation', 'b') + # TRANSLATORS: 'r' to refresh + refresh = C_('TUI|Spoke Navigation', 'r') + + return _(" Please make your choice from above ['%s' to quit | '%s' to begin installation |\n '%s' to refresh]: ") % \ + (quit, begin, refresh) def input(self, args, key): """Handle user input. Numbers are used to show a spoke, the rest is passed to the higher level for processing.""" @@ -101,7 +109,7 @@ class SummaryHub(TUIHub): # If we get a continue, check for unfinished spokes. If unfinished # don't continue # TRANSLATORS: 'b' to begin installation - if key == _('b'): + if key == C_('TUI|Spoke Navigation', 'b'): for spoke in self._spokes.values(): if not spoke.completed and spoke.mandatory: print(_("Please complete all spokes before continuing")) @@ -115,7 +123,7 @@ class SummaryHub(TUIHub): self.app.close_screen() return True # TRANSLATORS: 'c' to continue - elif key == _('c'): + elif key == C_('TUI|Spoke Navigation', 'c'): # Kind of a hack, but we want to ignore if anyone presses 'c' # which is the global TUI key to close the current screen return False diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index c4fe736..b09afab 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -627,7 +627,16 @@ class UIScreen(object): to skip further input processing :rtype: unicode|None """ - return _(u" Please make your choice from above ['q' to quit | 'c' to continue |\n 'r' to refresh]: ") + + # TRANSLATORS: 'q' to quit + quit = C_('TUI|Spoke Navigation', 'q'), + # TRANSLATORS:'c' to continue + forward = C_('TUI|Spoke Navigation', 'c'), + # TRANSLATORS:'r' to refresh + refresh = C_('TUI|Spoke Navigation', 'r') + + return _(u" Please make your choice from above ['%s' to quit | '%s' to continue |\n '%s' to refresh]: ") % \ + (quit, forward, refresh)
@property def app(self): diff --git a/pyanaconda/ui/tui/spokes/langsupport.py b/pyanaconda/ui/tui/spokes/langsupport.py index 0b1e836..6685bf3 100644 --- a/pyanaconda/ui/tui/spokes/langsupport.py +++ b/pyanaconda/ui/tui/spokes/langsupport.py @@ -24,7 +24,7 @@ from pyanaconda.ui.tui.spokes import NormalTUISpoke from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda import localization -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_
class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke): """ @@ -109,7 +109,7 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke): pass
# TRANSLATORS: 'b' to go back - if key.lower() == _("b"): + if key.lower() == C_('TUI|Spoke Navigation|Language Support', 'b'): self.app.switch_screen(self, None) return True else: @@ -117,7 +117,16 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
def prompt(self, args=None): """ Override default prompt with a custom prompt. """ - return _("Please select language support to install.\n[b to language list, c to continue, q to quit]: ") + + # TRANSLATORS: 'b' to go back + back = C_('TUI|Spoke Navigation|Language Support', 'b'), + # TRANSLATORS:'c' to continue + forward = C_('TUI|Spoke Navigation|Language Support', 'c'), + # TRANSLATORS:'q' to quit + quit = C_('TUI|Spoke Navigation|Language Support', 'q') + + return _("Please select language support to install.\n[%s to return to language list, %s to continue, %s to quit]: ") % \ + (back, forward, quit)
def apply(self): """ Store the selected langsupport locales """ diff --git a/pyanaconda/ui/tui/spokes/time_spoke.py b/pyanaconda/ui/tui/spokes/time_spoke.py index a6e59e3..b1c74d8 100644 --- a/pyanaconda/ui/tui/spokes/time_spoke.py +++ b/pyanaconda/ui/tui/spokes/time_spoke.py @@ -24,7 +24,7 @@ from pyanaconda.ui.tui.spokes import NormalTUISpoke from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda import timezone -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ from pyanaconda.constants_text import INPUT_PROCESSED
class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): @@ -110,7 +110,8 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): else: self.app.switch_screen(self, self._regions[index]) return INPUT_PROCESSED - elif key.lower() == "b": + # TRANSLATORS: 'b' to go back + elif key.lower() == C_('TUI|Spoke Navigation|Time Settings', 'b'): self.app.switch_screen(self, None) return INPUT_PROCESSED else: @@ -131,7 +132,12 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): return INPUT_PROCESSED
def prompt(self, args = None): - return _("Please select the timezone.\nUse numbers or type names directly [b to region list, q to quit]: ") + # TRANSLATORS: 'b' to go back + back = C_('TUI|Spoke Navigation|Time Settings', 'b'), + # TRANSLATORS:'q' to quit + quit = C_('TUI|Spoke Navigation|Time Settings', 'q') + + return _("Please select the timezone.\nUse numbers or type names directly [%s to region list, %s to quit]: ") % (back, quit)
def apply(self): self.data.timezone.timezone = self._selection diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 9fbb726..7ff00ee 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -19,7 +19,7 @@ # Red Hat Author(s): Martin Sivak msivak@redhat.com #
-from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ from pyanaconda.ui import common from pyanaconda.ui.tui import simpleline as tui from pyanaconda.constants_text import INPUT_PROCESSED @@ -128,15 +128,22 @@ class YesNoDialog(tui.UIScreen): return True
def prompt(self, args = None): - return _("Please respond 'yes' or 'no': ") + return _("Please respond '%(yes)s' or '%(no)s': ") % { + # TRANSLATORS: 'yes' as positive reply + "yes": C_('TUI|Spoke Navigation', 'yes'), + # TRANSLATORS: 'no' as negative reply + "no": C_('TUI|Spoke Navigation', 'no') + }
def input(self, args, key): - if key == _("yes"): + # TRANSLATORS: 'yes' as positive reply + if key == C_('TUI|Spoke Navigation', 'yes'): self._response = True self.close() return None
- elif key == _("no"): + # TRANSLATORS: 'no' as negative reply + elif key == C_('TUI|Spoke Navigation', 'no'): self._response = False self.close() return None
On 07/27/2015 02:42 PM, Martin Kolman wrote:
What happens is that we have the following translatable strings:
- "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]"
- "q"
- "c"
- "r"
All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately, simplified example:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com
pyanaconda/ui/tui/hubs/__init__.py | 11 +++++++++-- pyanaconda/ui/tui/hubs/summary.py | 16 ++++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 11 ++++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 15 ++++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 63 insertions(+), 17 deletions(-)
My only complaint now is the stuff that gettext_warnings.sh is going to complain about: for each for the "Please make your choice..."/"Please select..." strings, use named format parameters and a dictionary instead of just bare %s's.
On Mon, 2015-07-27 at 16:44 -0400, David Shea wrote:
On 07/27/2015 02:42 PM, Martin Kolman wrote:
What happens is that we have the following translatable strings:
- "Please make your choice from above ['q' to quit | 'c' to
continue | 'r' to refresh]"
- "q"
- "c"
- "r"
All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately, simplified example:
_("Please make your choice from above ['%s' to quit | '%s' to continue | '%s' to refresh]") % \ (_("q"), _("c"), _("r"))
This makes sure that we will always ask the user to type the same keystroke that is actually displayed.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com
pyanaconda/ui/tui/hubs/__init__.py | 11 +++++++++-- pyanaconda/ui/tui/hubs/summary.py | 16 ++++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 11 ++++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 15 ++++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 63 insertions(+), 17 deletions(-)
My only complaint now is the stuff that gettext_warnings.sh is going to complain about:
There was no gettext_warnings.sh when I wrote this. ;-)
for each for the "Please make your choice..."/"Please select..." strings, use named format parameters and a dictionary instead of just bare %s's.
On to v3! :D
anaconda-patches mailing list anaconda-patches@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
Changes from v2: - use dictionary substitution to make translations more evident
Martin Kolman (1): Prevent incomplete translations from making the TUI unusable (#1235617)
pyanaconda/ui/tui/hubs/__init__.py | 13 ++++++++++--- pyanaconda/ui/tui/hubs/summary.py | 15 +++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 10 +++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 13 ++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 60 insertions(+), 18 deletions(-)
What happens is that we have the following translatable strings: - "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]" - "q" - "c" - "r" All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately, for example:
return _(" Please make your choice from above ['%(quit)s' to quit | '%(begin)s' to begin installation |\n '%(refresh)s' to refresh]: ") % { # TRANSLATORS: 'q' to quit 'quit': C_('TUI|Spoke Navigation', 'q'), # TRANSLATORS: 'b' to begin installation 'begin': C_('TUI|Spoke Navigation', 'b'), # TRANSLATORS: 'r' to refresh 'refresh': C_('TUI|Spoke Navigation', 'r') }
This makes sure that we will always ask the user to type the same keystroke that is actually displayed. It also assures that the translators will actually know by using the TRANSLATORS: comments, translations contexts and dictionary substitution.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com --- pyanaconda/ui/tui/hubs/__init__.py | 13 ++++++++++--- pyanaconda/ui/tui/hubs/summary.py | 15 +++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 10 +++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 13 ++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 60 insertions(+), 18 deletions(-)
diff --git a/pyanaconda/ui/tui/hubs/__init__.py b/pyanaconda/ui/tui/hubs/__init__.py index 82b49fe..18ae5e9 100644 --- a/pyanaconda/ui/tui/hubs/__init__.py +++ b/pyanaconda/ui/tui/hubs/__init__.py @@ -128,8 +128,15 @@ class TUIHub(TUIObject, common.Hub): :rtype: str|None """ if self._spoke_count == 1: - single_spoke_title = self._spokes.values()[0].title - return _(u" Please make your choice from [ '1' to enter the %s spoke | 'q' to quit |\n" - " 'c' to continue | 'r' to refresh]: ") % single_spoke_title + return _(u" Please make your choice from [ '1' to enter the %(spoke_title)s spoke | '%(quit)s' to quit |\n" + " '%(continue)s' to continue | '%(refresh)s' to refresh]: ") % { + 'spoke_title': self._spokes.values()[0].title, + # TRANSLATORS: 'q' to quit + 'quit': C_('TUI|Spoke Navigation', 'q'), + # TRANSLATORS:'c' to continue + 'continue': C_('TUI|Spoke Navigation', 'c'), + # TRANSLATORS:'r' to refresh + 'refresh': C_('TUI|Spoke Navigation', 'r') + } else: return super(TUIHub, self).prompt(args) diff --git a/pyanaconda/ui/tui/hubs/summary.py b/pyanaconda/ui/tui/hubs/summary.py index 5d414ee..c8bd6b7 100644 --- a/pyanaconda/ui/tui/hubs/summary.py +++ b/pyanaconda/ui/tui/hubs/summary.py @@ -24,7 +24,7 @@ from pyanaconda.ui.lib.space import FileSystemSpaceChecker, DirInstallSpaceCheck from pyanaconda.ui.tui.hubs import TUIHub from pyanaconda.flags import flags from pyanaconda.errors import CmdlineError -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ import sys import time
@@ -87,7 +87,14 @@ class SummaryHub(TUIHub):
# override the default prompt since we want to offer the 'b' to begin # installation option here - return _(" Please make your choice from above ['q' to quit | 'b' to begin installation |\n 'r' to refresh]: ") + return _(" Please make your choice from above ['%(quit)s' to quit | '%(begin)s' to begin installation |\n '%(refresh)s' to refresh]: ") % { + # TRANSLATORS: 'q' to quit + 'quit': C_('TUI|Spoke Navigation', 'q'), + # TRANSLATORS: 'b' to begin installation + 'begin': C_('TUI|Spoke Navigation', 'b'), + # TRANSLATORS: 'r' to refresh + 'refresh': C_('TUI|Spoke Navigation', 'r') + }
def input(self, args, key): """Handle user input. Numbers are used to show a spoke, the rest is passed @@ -101,7 +108,7 @@ class SummaryHub(TUIHub): # If we get a continue, check for unfinished spokes. If unfinished # don't continue # TRANSLATORS: 'b' to begin installation - if key == _('b'): + if key == C_('TUI|Spoke Navigation', 'b'): for spoke in self._spokes.values(): if not spoke.completed and spoke.mandatory: print(_("Please complete all spokes before continuing")) @@ -115,7 +122,7 @@ class SummaryHub(TUIHub): self.app.close_screen() return True # TRANSLATORS: 'c' to continue - elif key == _('c'): + elif key == C_('TUI|Spoke Navigation', 'c'): # Kind of a hack, but we want to ignore if anyone presses 'c' # which is the global TUI key to close the current screen return False diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index c4fe736..3accc76 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -627,7 +627,15 @@ class UIScreen(object): to skip further input processing :rtype: unicode|None """ - return _(u" Please make your choice from above ['q' to quit | 'c' to continue |\n 'r' to refresh]: ") + + return _(u" Please make your choice from above ['%(quit)s' to quit | '%(continue)s' to continue |\n '%(refresh)s' to refresh]: ") % { + # TRANSLATORS: 'q' to quit + 'quit': C_('TUI|Spoke Navigation', 'q'), + # TRANSLATORS:'c' to continue + 'continue': C_('TUI|Spoke Navigation', 'c'), + # TRANSLATORS:'r' to refresh + 'refresh': C_('TUI|Spoke Navigation', 'r') + }
@property def app(self): diff --git a/pyanaconda/ui/tui/spokes/langsupport.py b/pyanaconda/ui/tui/spokes/langsupport.py index 0b1e836..b959de4 100644 --- a/pyanaconda/ui/tui/spokes/langsupport.py +++ b/pyanaconda/ui/tui/spokes/langsupport.py @@ -24,7 +24,7 @@ from pyanaconda.ui.tui.spokes import NormalTUISpoke from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda import localization -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_
class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke): """ @@ -109,7 +109,7 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke): pass
# TRANSLATORS: 'b' to go back - if key.lower() == _("b"): + if key.lower() == C_('TUI|Spoke Navigation|Language Support', 'b'): self.app.switch_screen(self, None) return True else: @@ -117,7 +117,14 @@ class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
def prompt(self, args=None): """ Override default prompt with a custom prompt. """ - return _("Please select language support to install.\n[b to language list, c to continue, q to quit]: ") + return _("Please select language support to install.\n[%(back)s to return to language list, %(continue)s to continue, %(quit)s to quit]: ") % { + # TRANSLATORS: 'b' to go back + 'back': C_('TUI|Spoke Navigation|Language Support', 'b'), + # TRANSLATORS:'c' to continue + 'continue': C_('TUI|Spoke Navigation|Language Support', 'c'), + # TRANSLATORS:'q' to quit + 'quit': C_('TUI|Spoke Navigation|Language Support', 'q') + }
def apply(self): """ Store the selected langsupport locales """ diff --git a/pyanaconda/ui/tui/spokes/time_spoke.py b/pyanaconda/ui/tui/spokes/time_spoke.py index a6e59e3..431ae23 100644 --- a/pyanaconda/ui/tui/spokes/time_spoke.py +++ b/pyanaconda/ui/tui/spokes/time_spoke.py @@ -24,7 +24,7 @@ from pyanaconda.ui.tui.spokes import NormalTUISpoke from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda import timezone -from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ from pyanaconda.constants_text import INPUT_PROCESSED
class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): @@ -110,7 +110,8 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): else: self.app.switch_screen(self, self._regions[index]) return INPUT_PROCESSED - elif key.lower() == "b": + # TRANSLATORS: 'b' to go back + elif key.lower() == C_('TUI|Spoke Navigation|Time Settings', 'b'): self.app.switch_screen(self, None) return INPUT_PROCESSED else: @@ -131,7 +132,12 @@ class TimeZoneSpoke(FirstbootSpokeMixIn, NormalTUISpoke): return INPUT_PROCESSED
def prompt(self, args = None): - return _("Please select the timezone.\nUse numbers or type names directly [b to region list, q to quit]: ") + return _("Please select the timezone.\nUse numbers or type names directly [%(back)s to region list, %(quit)s to quit]: ") % { + # TRANSLATORS: 'b' to go back + 'back': C_('TUI|Spoke Navigation|Time Settings', 'b'), + # TRANSLATORS:'q' to quit + 'quit': C_('TUI|Spoke Navigation|Time Settings', 'q') + }
def apply(self): self.data.timezone.timezone = self._selection diff --git a/pyanaconda/ui/tui/tuiobject.py b/pyanaconda/ui/tui/tuiobject.py index 9fbb726..7ff00ee 100644 --- a/pyanaconda/ui/tui/tuiobject.py +++ b/pyanaconda/ui/tui/tuiobject.py @@ -19,7 +19,7 @@ # Red Hat Author(s): Martin Sivak msivak@redhat.com #
-from pyanaconda.i18n import N_, _ +from pyanaconda.i18n import N_, _, C_ from pyanaconda.ui import common from pyanaconda.ui.tui import simpleline as tui from pyanaconda.constants_text import INPUT_PROCESSED @@ -128,15 +128,22 @@ class YesNoDialog(tui.UIScreen): return True
def prompt(self, args = None): - return _("Please respond 'yes' or 'no': ") + return _("Please respond '%(yes)s' or '%(no)s': ") % { + # TRANSLATORS: 'yes' as positive reply + "yes": C_('TUI|Spoke Navigation', 'yes'), + # TRANSLATORS: 'no' as negative reply + "no": C_('TUI|Spoke Navigation', 'no') + }
def input(self, args, key): - if key == _("yes"): + # TRANSLATORS: 'yes' as positive reply + if key == C_('TUI|Spoke Navigation', 'yes'): self._response = True self.close() return None
- elif key == _("no"): + # TRANSLATORS: 'no' as negative reply + elif key == C_('TUI|Spoke Navigation', 'no'): self._response = False self.close() return None
On 07/28/2015 11:56 AM, Martin Kolman wrote:
What happens is that we have the following translatable strings:
- "Please make your choice from above ['q' to quit | 'c' to continue | 'r' to refresh]"
- "q"
- "c"
- "r"
All of these strings can be translated *separately* and in many partial translations *some of them are translated while some are not*.
The end result is that we might be telling the user to press say "p" for "pokračovat" but me might actually expect "c" for continue, because the "c" string was not translated to "p".
This affects both the Anaconda and Initial Setup TUIs and can make the UI totally unusable.
The fix for this is to remove the keystrokes from the long strings and replace them by string formatting operators & do the keystroke transformation separately, for example:
return _(" Please make your choice from above ['%(quit)s' to quit | '%(begin)s' to begin installation |\n '%(refresh)s' to refresh]: ") % { # TRANSLATORS: 'q' to quit 'quit': C_('TUI|Spoke Navigation', 'q'), # TRANSLATORS: 'b' to begin installation 'begin': C_('TUI|Spoke Navigation', 'b'), # TRANSLATORS: 'r' to refresh 'refresh': C_('TUI|Spoke Navigation', 'r') }
This makes sure that we will always ask the user to type the same keystroke that is actually displayed. It also assures that the translators will actually know by using the TRANSLATORS: comments, translations contexts and dictionary substitution.
Resolves: rhbz#1235617 Signed-off-by: Martin Kolman mkolman@redhat.com
pyanaconda/ui/tui/hubs/__init__.py | 13 ++++++++++--- pyanaconda/ui/tui/hubs/summary.py | 15 +++++++++++---- pyanaconda/ui/tui/simpleline/base.py | 10 +++++++++- pyanaconda/ui/tui/spokes/langsupport.py | 13 ++++++++++--- pyanaconda/ui/tui/spokes/time_spoke.py | 12 +++++++++--- pyanaconda/ui/tui/tuiobject.py | 15 +++++++++++---- 6 files changed, 60 insertions(+), 18 deletions(-)
Ack.
anaconda-patches@lists.fedorahosted.org