changeset 36:e6f81aa329b1

Introduced i18n support; german and english translation available.
author Ingo Weinzierl <ingo_weinzierl@web.de>
date Sat, 02 Oct 2010 22:22:35 +0200
parents cfa9e755a5d2
children 68cc10d082ab
files ChangeLog getan/config.py getan/resources.py getan/states.py getan/view.py po/Makefile po/README po/de.po
diffstat 8 files changed, 269 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Sep 05 15:54:44 2010 +0200
+++ b/ChangeLog	Sat Oct 02 22:22:35 2010 +0200
@@ -1,3 +1,21 @@
+2010-10-02  Ingo Weinzierl <ingo.weinzierl@intevation.de>
+
+	* getan/config.py: Setup the language.
+
+	* getan/resources.py: New module that should serve functions for working
+	  with resources. Currently, there is just a single function to get a
+	  translated text based on gettext.
+
+	* getan/states.py,
+	  getan/view.py: Replaced hard coded texts with gettext calls.
+
+	* po/Makefile,
+	  po/README,
+	  po/de.po: Makefile, description and german translation for gettext.
+
+	  NOTE: It is necessary to call 'make mo' in the po directory to enable the
+	  german language.
+
 2010-09-02  Ingo Weinzierl <ingo.weinzierl@intevation.de>
 
 	  ISSUE1575
--- a/getan/config.py	Sun Sep 05 15:54:44 2010 +0200
+++ b/getan/config.py	Sat Oct 02 22:22:35 2010 +0200
@@ -7,13 +7,35 @@
 # For details see LICENSE coming with the source of 'getan'.
 #
 
+import locale
 import logging
+import os
 
 logger = None
 
 def initialize():
+    setup_logging()
+    setup_locale()
+
+
+def setup_logging():
     logging.basicConfig(level=logging.INFO,
                         format='%(asctime)s %(levelname)s %(message)s',
                         filename='getan.log',
                         filemode='w')
     logger = logging.getLogger()
+
+
+def setup_locale():
+    for var in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
+        if var in os.environ:
+            break
+    else:
+        default_locale = locale.getdefaultlocale()
+        # The default is normally a tuple of two strings.  It may
+        # contain None, objects under some circumstances, though.
+        if len(default_locale) > 1:
+            lang = default_locale[0]
+            if isinstance(lang, str):
+                os.environ["LANG"] = lang
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/getan/resources.py	Sat Oct 02 22:22:35 2010 +0200
@@ -0,0 +1,13 @@
+import gettext as py_gettext
+import os
+import sys
+
+_base_dir = os.path.join(os.path.dirname(__file__), os.pardir)
+share_dir = os.path.join(_base_dir, "share")
+
+def gettext(message):
+    modir = os.path.join(share_dir, "locale")
+    t = py_gettext.translation("getan", modir, fallback=True)
+
+    return t.ugettext(message)
+
--- a/getan/states.py	Sun Sep 05 15:54:44 2010 +0200
+++ b/getan/states.py	Sat Oct 02 22:22:35 2010 +0200
@@ -12,7 +12,8 @@
 import signal
 from   datetime import datetime, timedelta
 
-from getan.utils import human_time
+from getan.resources import gettext as _
+from getan.utils     import human_time
 
 logger = logging.getLogger()
 
@@ -44,7 +45,7 @@
 
 class PausedProjectsState(ProjectState):
     messages = {
-        'choose_proj': u" Wählen Sie ein Projekt:"
+        'choose_proj': _('Choose a project: '),
     }
 
     def keypress(self, key):
@@ -94,8 +95,8 @@
 
 class ExitState(ProjectState):
     messages = {
-        'quit'  : u" Wirklich beenden? (y/n)",
-        'choose': u" Wählen Sie ein Projekt:"
+        'quit'  : _(" Really quit? (y/n)"),
+        'choose': _(" Choose a project:")
     }
 
     def __init__(self, controller, view):
@@ -120,10 +121,12 @@
 
 class RunningProjectsState(ProjectState):
     messages = {
-        'description': u" Geben Sie eine Beschreibung ein: ",
-        'add_time'   : u" Geben Sie die zu addierende Zeit ein [min]: ",
-        'min_time'   : u" Geben Sie die zu abzuziehende Zeit ein [min]: ",
-        'paused'     : u" 'Space' zum Fortzusetzen",
+        'description': _("Enter a description: "),
+        'add_time'   : _("Enter time to add [min]: "),
+        'min_time'   : _("Enter time to subtract [min]: "),
+        'continue'   : _("Press 'Space' to continue."),
+        'running'    : _("Running ( %s ) on '%s'."),
+        'paused'     : _(" Break   ( %s )%s."),
     }
 
     sec         = 0
@@ -137,7 +140,7 @@
     def handle_signal(self, signum, frame):
         proj = self.view.item_in_focus()
         if not self.break_start:
-            self.controller.view.set_footer_text(" Running ( %s ) on '%s'" %
+            self.controller.view.set_footer_text(self.msg('running') %
                                                  (human_time(self.sec),
                                                   proj.desc),
                                                  'running')
@@ -145,9 +148,9 @@
             self.sec = self.sec + 1
         else:
             self.view.set_footer_text(
-                ' Break   ( %s )%s' %
+                self.msg('paused') %
                 (human_time((datetime.now()-self.break_start).seconds),
-                 self.msg('paused')),
+                 self.msg('continue')),
                 'paused_running')
             self.controller.view.loop.draw_screen()
 
@@ -164,7 +167,7 @@
             return self.stop()
         if '+' in key:
             self.view.set_footer_text(self.msg('add_time'),
-                                                 'question', 1)
+                                               'question', 1)
             self.view.frame.set_focus('footer')
             return AddTimeState(self.controller, self.view, self)
         if '-' in key:
@@ -283,7 +286,7 @@
 
 class DescriptionProjectsState(HandleUserInputState):
     messages = {
-        'choose_proj': u" Wählen Sie ein Projekt."
+        'choose_proj': _(" Choose a project."),
     }
 
     def enter(self):
@@ -361,7 +364,7 @@
 
 class DeleteEntryState(EntryListState):
     messages = {
-        'delete'  : u" Wirklich löschen? (y/n)",
+        'delete'  : _("Really delete this entry? (y/n)"),
     }
 
     def __init__(self, state, controller, view):
@@ -392,8 +395,8 @@
 
 class MoveEntryState(EntryListState):
     messages = {
-        'project': u" In welches Projekt möchten Sie die Einträge verschieben?",
-        'really':  u" Sind sie sich sicher? (y/n)",
+        'project': _(" Into which project do you want to move these entries?"),
+        'really':  _(" Are you sure? (> %s) (y/n)"),
     }
 
     proj = None
--- a/getan/view.py	Sun Sep 05 15:54:44 2010 +0200
+++ b/getan/view.py	Sat Oct 02 22:22:35 2010 +0200
@@ -12,15 +12,9 @@
 import urwid
 import urwid.raw_display
 
-from getan.states import *
-from getan.utils  import short_time, format_datetime, format_time
-
-APP_TITLE          = u" .: getan next generation :."
-APP_REALLY_QUIT    = u" Wollen Sie wirklich beenden? "
-APP_CHOOSE_PROJECT = u" Wählen Sie ein Projekt: "
-
-PROJECTS_TITLE = u" Liste registrierter Projekte"
-ENTRIES_TITLE  = u" Liste der letzten Einträge"
+from getan.resources import gettext as _
+from getan.states    import *
+from getan.utils     import short_time, format_datetime, format_time
 
 logger = logging.getLogger()
 
@@ -96,11 +90,11 @@
 
 class ProjectNode(urwid.WidgetWrap):
     MODES = [
-        (0, 'Gesamt'),
-        (1, 'Jahr'),
-        (2, 'Monat'),
-        (3, 'Woche'),
-        (4, 'Tag')
+        (0, _('Total')),
+        (1, _('Year')),
+        (2, _('Month')),
+        (3, _('Week')),
+        (4, _('Day'))
     ]
 
     def __init__(self, proj, mode=3):
@@ -175,8 +169,8 @@
         self.controller   = controller
         self.raw_projects = rows
 
-        self.header    = urwid.LineBox(
-            urwid.AttrWrap(urwid.Text("\n%s\n" % PROJECTS_TITLE),'project_header'))
+        self.header    = urwid.LineBox(urwid.AttrWrap(urwid.Text("\n%s\n" %
+            _('List of registered projects')),'project_header'))
         self.footer    = urwid.Edit()
         self.rows      = [ProjectNode(x) for x in rows]
         self.listbox   = urwid.ListBox(urwid.SimpleListWalker(self.rows))
@@ -195,7 +189,7 @@
             if tmp and type(tmp) == int:
                 total += tmp
         self.frame.set_footer(urwid.AttrWrap(
-            urwid.Text(' Alle Projekte: %s %s'
+            urwid.Text(_(' All projects: %s %s')
                        % (proj.mode[1],human_time(total))), 'project_footer'))
 
     def update(self):
@@ -285,7 +279,7 @@
             ('fixed left', 1), ('fixed right', 1)))
         self.header    = urwid.LineBox(urwid.AttrWrap(urwid.Text(
             "\n%s\n"
-            % ENTRIES_TITLE),'entry_header'))
+            % _('Last entries')),'entry_header'))
         self.footer    = urwid.AttrWrap(urwid.Text(""), 'entry_footer')
         self.frame     = urwid.Frame(self.body, header=self.header,
                                      footer=self.footer)
@@ -332,8 +326,9 @@
             urwid.Padding(self.proj_list, ('fixed left',0),('fixed right',1)),
             self.entr_list], 0)
 
-        self.header   = urwid.AttrWrap(urwid.Text('%s\n' % APP_TITLE), 'header')
-        self.footer   = urwid.AttrWrap(urwid.Text(APP_CHOOSE_PROJECT),
+        self.header   = urwid.AttrWrap(urwid.Text('%s\n' % _('.: getan :.')),
+                                       'header')
+        self.footer   = urwid.AttrWrap(urwid.Text(_('Choose a project:')),
                                        'question')
         self.col_list = self.columns.widget_list
         view          = urwid.AttrWrap(self.columns, 'body')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/Makefile	Sat Oct 02 22:22:35 2010 +0200
@@ -0,0 +1,42 @@
+all:
+	@echo 'Usage:'
+	@echo '   make pot     create getan.pot'
+	@echo '   make merge   merge a new getan.pot with the *.po files'
+	@echo '   make mo      create the mo files'
+	@echo '   make stat    print statistics about the translation status'
+
+MO_DIR = ../share/locale
+DOMAIN = getan
+
+pot:
+	pygettext -o $(DOMAIN).pot ../getan/*.py
+
+merge:
+	for po in *.po; do \
+		lingua=`basename $$po .po`; \
+	        mv $$lingua.po $$lingua.old.po; \
+		if msgmerge -o $$lingua.po $$lingua.old.po $(DOMAIN).pot; then\
+		    rm $$lingua.old.po; \
+		else \
+		    rm -f $$lingua.po; \
+		    mv $$lingua.old.po $$lingua.po; \
+		fi \
+	done
+
+
+mo:
+	for po in *.po; do\
+		lingua=`basename $$po .po`; \
+		install -d $(MO_DIR)/$$lingua/LC_MESSAGES/ ; \
+		echo -n $$po": "; \
+		msgfmt --statistics \
+			-o $(MO_DIR)/$$lingua/LC_MESSAGES/$(DOMAIN).mo $$po ;\
+	done
+
+
+stat:
+	@for po in *.po; do\
+		echo -n $$po": "; \
+		msgfmt --statistics -o /dev/null $$po 2>&1 \
+		 | sed -e 's/ \(messages*\|translations*\)//g' -e 's/\.$$//' ; \
+	done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/README	Sat Oct 02 22:22:35 2010 +0200
@@ -0,0 +1,34 @@
+Getan Message Translations
+==============================
+
+The message catalogs are managed with the following software:
+
+ - pygettext (usually distributed with Python)
+
+ - GNU gettext (msgmerge and msgfmt)
+
+
+Common tasks:
+
+ - Create/Update getan.pot-File when the sources were edited and
+   messages have been added or changed and after a fresh checkout
+
+     $ make pot
+
+ - When the getan.pot has been changed, run
+
+     $ make merge
+
+   to merge the changes in the pot file with the messages already in the
+   *.po files.
+
+ - When the translations in the po files have been changed, run
+
+     $ make mo
+
+   to generate new .mo files under ../share/locale
+
+ - The make mo command prints statistics about the translations.  To get
+   a less verbose version use this:
+
+     $ make stat
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/po/de.po	Sat Oct 02 22:22:35 2010 +0200
@@ -0,0 +1,106 @@
+# German translation for getan.
+# Copyright (C) 2010 Intevation GmbH
+# Ingo Weinzierl <ingo@intevation.de>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Getan HG Repository\n"
+"POT-Creation-Date: 2010-09-25 01:34+CEST\n"
+"Last-Translator: Ingo Weinzierl <ingo@intevation.de>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Generated-By: pygettext.py 1.5\n"
+
+
+#: ../getan/states.py:48
+msgid "Choose a project: "
+msgstr "Wählen Sie ein Projekt:"
+
+#: ../getan/states.py:98
+msgid " Really quit? (y/n)"
+msgstr " Wirklich beenden? (y/n)"
+
+#: ../getan/states.py:99
+msgid " Choose a project:"
+msgstr "Wählen Sie ein Projekt:"
+
+#: ../getan/states.py:124
+msgid "Enter a description: "
+msgstr "Geben Sie eine Beschreibung ein: "
+
+#: ../getan/states.py:125
+msgid "Enter time to add [min]: "
+msgstr "Zeit zum Addieren eingeben [min]: "
+
+#: ../getan/states.py:126
+msgid "Enter time to subtract [min]: "
+msgstr "Zeit zum Subtrahieren eingeben [min]: "
+
+#: ../getan/states.py:127
+msgid "Press 'Space' to continue."
+msgstr "Drücken Sie 'Leertaste' um fortzusetzen."
+
+#: ../getan/states.py:128
+msgid "Running ( %s ) on '%s'."
+msgstr "Sie arbeiten ( %s ) an '%s'."
+
+#: ../getan/states.py:129
+msgid " Break   ( %s )%s."
+msgstr " Pause   ( %s )%s."
+
+#: ../getan/states.py:289
+msgid " Choose a project."
+msgstr "Wählen Sie ein Projekt:"
+
+#: ../getan/states.py:367
+msgid "Really delete this entry? (y/n)"
+msgstr "Wollen Sie diesen Eintrag wirklich löschen? (y/n)"
+
+#: ../getan/states.py:398
+msgid " Into which project do you want to move these entries?"
+msgstr "In welches Projekt sollen die Einträge verschoben werden? > %s"
+
+#: ../getan/states.py:399
+msgid " Are you sure? (> %s) (y/n)"
+msgstr " Sind Sie sicher? (> %s) (y/n)"
+
+#: ../getan/view.py:93
+msgid "Total"
+msgstr "Gesamt"
+
+#: ../getan/view.py:94
+msgid "Year"
+msgstr "Jahr"
+
+#: ../getan/view.py:95
+msgid "Month"
+msgstr "Monat"
+
+#: ../getan/view.py:96
+msgid "Week"
+msgstr "Woche"
+
+#: ../getan/view.py:97
+msgid "Day"
+msgstr "Tag"
+
+#: ../getan/view.py:173
+msgid "List of registered projects"
+msgstr "Liste der registrierten Projekte"
+
+#: ../getan/view.py:192
+msgid " All projects: %s %s"
+msgstr "Alle Projekte: %s %s"
+
+#: ../getan/view.py:282
+msgid "Last entries"
+msgstr "Letzte Einträge"
+
+#: ../getan/view.py:329
+msgid ".: getan :."
+msgstr ".: getan :."
+
+#: ../getan/view.py:331
+msgid "Choose a project:"
+msgstr "Wählen Sie ein Projekt:"
+
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)