Merge lp:~mtrovo/pomodoro-indicator/translation into lp:~marcosvanetta/pomodoro-indicator/master

Proposed by Moises Trovo
Status: Merged
Merge reported by: Moises Trovo
Merged at revision: not available
Proposed branch: lp:~mtrovo/pomodoro-indicator/translation
Merge into: lp:~marcosvanetta/pomodoro-indicator/master
Diff against target: 672 lines (+484/-40)
9 files modified
.bzrignore (+2/-0)
.gitignore (+0/-6)
msgfmt.py (+221/-0)
po/pomodoro-indicator.pot (+65/-0)
po/pt_BR.po (+66/-0)
pomodoro/configuration.py (+2/-0)
pomodoro/pomodoro_state.py (+1/-0)
pomodoro/visual.py (+29/-14)
setup.py (+98/-20)
To merge this branch: bzr merge lp:~mtrovo/pomodoro-indicator/translation
Reviewer Review Type Date Requested Status
malev i18n Approve
Review via email: mp+74692@code.launchpad.net

Description of the change

Fixed bugs
#844079 - Pause menu button was starting another timer instead of pausing the existing one.
#842118 - Localisation with gettext added to project and a translation for Portuguese (Brazil) was added.

To test the code with a specific locale please set the LANGUAGE variable before running the pomodoro-indicator

$ LANGUAGE=pt_BR:pt
$ bin/pomodoro-indicator

To configure the project as translatable within launchpad please refer to this tutorial https://help.launchpad.net/Translations/YourProject.

If you need any help with the fixes or the project setup please contact me.

Regards.

To post a comment you must log in.
Revision history for this message
malev (marcosvanetta) wrote :

I like it!

review: Approve (i18n)
19. By Moises Trovo

merged with ~mtrovo/main branch

Revision history for this message
Moises Trovo (mtrovo) wrote :

This merge is available on trunk but I will need to change the directory structure so it works with Launchpad Translations.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2011-09-24 16:35:25 +0000
4@@ -0,0 +1,2 @@
5+build
6+.ninjaproject
7
8=== removed file '.gitignore'
9--- .gitignore 2011-08-14 23:09:33 +0000
10+++ .gitignore 1970-01-01 00:00:00 +0000
11@@ -1,6 +0,0 @@
12-*.deb
13-*.pyc
14-*.mo
15-*.po~
16-debian/
17-
18
19=== added file 'msgfmt.py'
20--- msgfmt.py 1970-01-01 00:00:00 +0000
21+++ msgfmt.py 2011-09-24 16:35:25 +0000
22@@ -0,0 +1,221 @@
23+#! /usr/bin/env python
24+# -*- coding: iso-8859-1 -*-
25+# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
26+
27+"""Generate binary message catalog from textual translation description.
28+
29+This program converts a textual Uniforum-style message catalog (.po file) into
30+a binary GNU catalog (.mo file). This is essentially the same function as the
31+GNU msgfmt program, however, it is a simpler implementation.
32+
33+Usage: msgfmt.py [OPTIONS] filename.po
34+
35+Options:
36+ -o file
37+ --output-file=file
38+ Specify the output file to write to. If omitted, output will go to a
39+ file named filename.mo (based off the input file name).
40+
41+ -h
42+ --help
43+ Print this message and exit.
44+
45+ -V
46+ --version
47+ Display version information and exit.
48+"""
49+
50+import sys
51+import os
52+import getopt
53+import struct
54+import array
55+
56+__version__ = "1.1"
57+
58+MESSAGES = {}
59+
60+
61+def usage(code, msg=''):
62+ print >> sys.stderr, __doc__
63+ if msg:
64+ print >> sys.stderr, msg
65+ sys.exit(code)
66+
67+
68+def add(id, str, fuzzy):
69+ "Add a non-fuzzy translation to the dictionary."
70+ global MESSAGES
71+ if not fuzzy and str:
72+ MESSAGES[id] = str
73+
74+
75+def generate():
76+ "Return the generated output."
77+ global MESSAGES
78+ keys = MESSAGES.keys()
79+ # the keys are sorted in the .mo file
80+ keys.sort()
81+ offsets = []
82+ ids = strs = ''
83+ for id in keys:
84+ # For each string, we need size and file offset. Each string is NUL
85+ # terminated; the NUL does not count into the size.
86+ offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
87+ ids += id + '\0'
88+ strs += MESSAGES[id] + '\0'
89+ output = ''
90+ # The header is 7 32-bit unsigned integers. We don't use hash tables, so
91+ # the keys start right after the index tables.
92+ # translated string.
93+ keystart = 7*4+16*len(keys)
94+ # and the values start after the keys
95+ valuestart = keystart + len(ids)
96+ koffsets = []
97+ voffsets = []
98+ # The string table first has the list of keys, then the list of values.
99+ # Each entry has first the size of the string, then the file offset.
100+ for o1, l1, o2, l2 in offsets:
101+ koffsets += [l1, o1+keystart]
102+ voffsets += [l2, o2+valuestart]
103+ offsets = koffsets + voffsets
104+ output = struct.pack("Iiiiiii",
105+ 0x950412deL, # Magic
106+ 0, # Version
107+ len(keys), # # of entries
108+ 7*4, # start of key index
109+ 7*4+len(keys)*8, # start of value index
110+ 0, 0) # size and offset of hash table
111+ output += array.array("i", offsets).tostring()
112+ output += ids
113+ output += strs
114+ return output
115+
116+def make(filename, outfile):
117+ ID = 1
118+ STR = 2
119+
120+ # Compute .mo name from .po name and arguments
121+ if filename.endswith('.po'):
122+ infile = filename
123+ else:
124+ infile = filename + '.po'
125+ if outfile is None:
126+ outfile = os.path.splitext(infile)[0] + '.mo'
127+
128+ try:
129+ lines = open(infile).readlines()
130+ except IOError, msg:
131+ print >> sys.stderr, msg
132+ sys.exit(1)
133+
134+ section = None
135+ fuzzy = 0
136+
137+ # Parse the catalog
138+ lno = 0
139+ for l in lines:
140+ lno += 1
141+ # If we get a comment line after a msgstr, this is a new entry
142+ if l[0] == '#' and section == STR:
143+ add(msgid, msgstr, fuzzy)
144+ section = None
145+ fuzzy = 0
146+ # Record a fuzzy mark
147+ if l[:2] == '#,' and 'fuzzy' in l:
148+ fuzzy = 1
149+ # Skip comments
150+ if l[0] == '#':
151+ continue
152+ # Now we are in a msgid section, output previous section
153+ if l.startswith('msgid') and not l.startswith('msgid_plural'):
154+ if section == STR:
155+ add(msgid, msgstr, fuzzy)
156+ section = ID
157+ l = l[5:]
158+ msgid = msgstr = ''
159+ is_plural = False
160+ # This is a message with plural forms
161+ elif l.startswith('msgid_plural'):
162+ if section != ID:
163+ print >> sys.stderr, 'msgid_plural not preceeded by msgid on %s:%d' %\
164+ (infile, lno)
165+ sys.exit(1)
166+ l = l[12:]
167+ msgid += '\0' # separator of singular and plural
168+ is_plural = True
169+ # Now we are in a msgstr section
170+ elif l.startswith('msgstr'):
171+ section = STR
172+ if l.startswith('msgstr['):
173+ if not is_plural:
174+ print >> sys.stderr, 'plural without msgid_plural on %s:%d' %\
175+ (infile, lno)
176+ sys.exit(1)
177+ l = l.split(']', 1)[1]
178+ if msgstr:
179+ msgstr += '\0' # Separator of the various plural forms
180+ else:
181+ if is_plural:
182+ print >> sys.stderr, 'indexed msgstr required for plural on %s:%d' %\
183+ (infile, lno)
184+ sys.exit(1)
185+ l = l[6:]
186+ # Skip empty lines
187+ l = l.strip()
188+ if not l:
189+ continue
190+ # XXX: Does this always follow Python escape semantics?
191+ l = eval(l)
192+ if section == ID:
193+ msgid += l
194+ elif section == STR:
195+ msgstr += l
196+ else:
197+ print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
198+ 'before:'
199+ print >> sys.stderr, l
200+ sys.exit(1)
201+ # Add last entry
202+ if section == STR:
203+ add(msgid, msgstr, fuzzy)
204+
205+ # Compute output
206+ output = generate()
207+
208+ try:
209+ open(outfile,"wb").write(output)
210+ except IOError,msg:
211+ print >> sys.stderr, msg
212+
213+
214+def main():
215+ try:
216+ opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
217+ ['help', 'version', 'output-file='])
218+ except getopt.error, msg:
219+ usage(1, msg)
220+
221+ outfile = None
222+ # parse options
223+ for opt, arg in opts:
224+ if opt in ('-h', '--help'):
225+ usage(0)
226+ elif opt in ('-V', '--version'):
227+ print >> sys.stderr, "msgfmt.py", __version__
228+ sys.exit(0)
229+ elif opt in ('-o', '--output-file'):
230+ outfile = arg
231+ # do it
232+ if not args:
233+ print >> sys.stderr, 'No input file given'
234+ print >> sys.stderr, "Try `msgfmt --help' for more information."
235+ return
236+
237+ for filename in args:
238+ make(filename, outfile)
239+
240+
241+if __name__ == '__main__':
242+ main()
243+
244
245=== added directory 'po'
246=== added file 'po/pomodoro-indicator.pot'
247--- po/pomodoro-indicator.pot 1970-01-01 00:00:00 +0000
248+++ po/pomodoro-indicator.pot 2011-09-24 16:35:25 +0000
249@@ -0,0 +1,65 @@
250+# SOME DESCRIPTIVE TITLE.
251+# Copyright (C) YEAR ORGANIZATION
252+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
253+#
254+msgid ""
255+msgstr ""
256+"Project-Id-Version: PACKAGE VERSION\n"
257+"POT-Creation-Date: 2011-09-08 15:25+BRT\n"
258+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
259+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
260+"Language-Team: LANGUAGE <LL@li.org>\n"
261+"MIME-Version: 1.0\n"
262+"Content-Type: text/plain; charset=CHARSET\n"
263+"Content-Transfer-Encoding: ENCODING\n"
264+"Generated-By: pygettext.py 1.5\n"
265+
266+
267+#: ../visual.py:49 ../visual.py:106
268+msgid "Waiting"
269+msgstr ""
270+
271+#: ../visual.py:50
272+msgid "Working"
273+msgstr ""
274+
275+#: ../visual.py:51
276+msgid "Resting"
277+msgstr ""
278+
279+#: ../visual.py:52
280+msgid "Paused"
281+msgstr ""
282+
283+#: ../visual.py:67
284+msgid "Pomodoro"
285+msgstr ""
286+
287+#: ../visual.py:75
288+msgid "You should start working."
289+msgstr ""
290+
291+#: ../visual.py:77
292+msgid "You can take a break now."
293+msgstr ""
294+
295+#: ../visual.py:110
296+msgid "Start"
297+msgstr ""
298+
299+#: ../visual.py:111
300+msgid "Pause"
301+msgstr ""
302+
303+#: ../visual.py:112
304+msgid "Resume"
305+msgstr ""
306+
307+#: ../visual.py:113
308+msgid "Stop"
309+msgstr ""
310+
311+#: ../visual.py:114
312+msgid "Quit"
313+msgstr ""
314+
315
316=== added directory 'po/pt_BR'
317=== added file 'po/pt_BR.po'
318--- po/pt_BR.po 1970-01-01 00:00:00 +0000
319+++ po/pt_BR.po 2011-09-24 16:35:25 +0000
320@@ -0,0 +1,66 @@
321+# English translations for PACKAGE package.
322+# Copyright (C) 2011 ORGANIZATION
323+# Moises Trovo <moises.trovo@gmail.com>, 2011.
324+#
325+msgid ""
326+msgstr ""
327+"Project-Id-Version: PACKAGE VERSION\n"
328+"POT-Creation-Date: 2011-09-08 15:25+BRT\n"
329+"PO-Revision-Date: 2011-09-08 15:26-0300\n"
330+"Last-Translator: Moises Trovo <moises.trovo@gmail.com>\n"
331+"Language-Team: English\n"
332+"MIME-Version: 1.0\n"
333+"Content-Type: text/plain; charset=UTF-8\n"
334+"Content-Transfer-Encoding: 8bit\n"
335+"Generated-By: pygettext.py 1.5\n"
336+"Language: en_US\n"
337+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
338+
339+#: ../visual.py:49 ../visual.py:106
340+msgid "Waiting"
341+msgstr "Aguardando"
342+
343+#: ../visual.py:50
344+msgid "Working"
345+msgstr "Trabalhando"
346+
347+#: ../visual.py:51
348+msgid "Resting"
349+msgstr "Descansando"
350+
351+#: ../visual.py:52
352+msgid "Paused"
353+msgstr "Pausado"
354+
355+#: ../visual.py:67
356+msgid "Pomodoro"
357+msgstr "Pomodoro"
358+
359+#: ../visual.py:75
360+msgid "You should start working."
361+msgstr "Você deve voltar a trabalhar."
362+
363+#: ../visual.py:77
364+msgid "You can take a break now."
365+msgstr "Você pode dar um intervalo agora."
366+
367+#: ../visual.py:110
368+msgid "Start"
369+msgstr "Iniciar"
370+
371+#: ../visual.py:111
372+msgid "Pause"
373+msgstr "Pausar"
374+
375+#: ../visual.py:112
376+msgid "Resume"
377+msgstr "Continuar"
378+
379+#: ../visual.py:113
380+msgid "Stop"
381+msgstr "Parar"
382+
383+#: ../visual.py:114
384+msgid "Quit"
385+msgstr "Sair"
386+
387
388=== added directory 'po/pt_BR/LC_MESSAGES'
389=== added file 'po/pt_BR/LC_MESSAGES/pomodoro.mo'
390Binary files po/pt_BR/LC_MESSAGES/pomodoro.mo 1970-01-01 00:00:00 +0000 and po/pt_BR/LC_MESSAGES/pomodoro.mo 2011-09-24 16:35:25 +0000 differ
391=== modified file 'pomodoro/configuration.py'
392--- pomodoro/configuration.py 2011-08-27 21:28:03 +0000
393+++ pomodoro/configuration.py 2011-09-24 16:35:25 +0000
394@@ -29,6 +29,7 @@
395 """
396
397 import os
398+import gettext
399
400 def base_directory():
401 """Returns the base directory"""
402@@ -40,3 +41,4 @@
403
404 if __name__ == "__main__":
405 print __doc__
406+
407
408=== modified file 'pomodoro/pomodoro_state.py'
409--- pomodoro/pomodoro_state.py 2011-08-29 03:06:07 +0000
410+++ pomodoro/pomodoro_state.py 2011-09-24 16:35:25 +0000
411@@ -237,3 +237,4 @@
412
413 if __name__ == "__main__":
414 print __doc__
415+
416
417=== modified file 'pomodoro/visual.py'
418--- pomodoro/visual.py 2011-08-29 03:06:07 +0000
419+++ pomodoro/visual.py 2011-09-24 16:35:25 +0000
420@@ -31,6 +31,13 @@
421 import sys
422 import pomodoro_state
423 import configuration
424+import gettext
425+
426+from gettext import gettext as _
427+
428+PROJECTNAME='pomodoro-indicator'
429+gettext.bindtextdomain(PROJECTNAME)
430+gettext.textdomain(PROJECTNAME)
431
432 """
433 Pomodoro's indicator
434@@ -39,6 +46,7 @@
435 # http://www.softicons.com/free-icons/food-drinks-icons/veggies-icons-by-icon-icon/tomato-icon
436
437 class PomodoroOSDNotificator:
438+
439 def __init__(self):
440 self.icon_directory = configuration.icon_directory()
441
442@@ -52,7 +60,7 @@
443 pynotify.init("icon-summary-body")
444 message = self.generate_message(state)
445 osd_box = pynotify.Notification(
446- "Pomodoro",
447+ _("Pomodoro"),
448 message,
449 self.big_icon()
450 )
451@@ -60,12 +68,17 @@
452
453 def generate_message(self, status):
454 if status == pomodoro_state.WORKING_STATE:
455- message = "You should start working."
456+ message = _("You should start working.")
457 elif status == pomodoro_state.RESTING_STATE:
458- message = "You can take a break now."
459+ message = _("You can take a break now.")
460 return message
461
462 class PomodoroIndicator:
463+ status_labels = { pomodoro_state.WAITING_STATE: _('Waiting'),
464+ pomodoro_state.WORKING_STATE: _('Working'),
465+ pomodoro_state.RESTING_STATE: _('Resting'),
466+ pomodoro_state.PAUSED_STATE: _('Paused')}
467+
468 def __init__(self):
469 self.pomodoro = pomodoro_state.PomodoroMachine()
470 self.notificator = PomodoroOSDNotificator()
471@@ -91,15 +104,15 @@
472 self.menu = gtk.Menu()
473 self.separator1 = gtk.SeparatorMenuItem()
474 self.separator2 = gtk.SeparatorMenuItem()
475- self.current_state_item = gtk.MenuItem("Waiting")
476+ self.current_state_item = gtk.MenuItem(_("Waiting"))
477 self.timer_item = gtk.MenuItem("00:00")
478
479 # Drawing buttons
480- self.start_item = gtk.MenuItem("Start")
481- self.pause_item = gtk.MenuItem("Pause")
482- self.resume_item = gtk.MenuItem("Resume")
483- self.stop_item = gtk.MenuItem("Stop")
484- self.quit_item = gtk.MenuItem("Quit")
485+ self.start_item = gtk.MenuItem(_("Start"))
486+ self.pause_item = gtk.MenuItem(_("Pause"))
487+ self.resume_item = gtk.MenuItem(_("Resume"))
488+ self.stop_item = gtk.MenuItem(_("Stop"))
489+ self.quit_item = gtk.MenuItem(_("Quit"))
490
491 self.state_visible_menu_items = {
492 pomodoro_state.WAITING_STATE : [self.start_item],
493@@ -154,7 +167,7 @@
494
495 def change_status_menu_item_label(self):
496 label = self.current_state_item.child
497- label.set_text(self.pomodoro.current_state().capitalize())
498+ label.set_text(self.status_labels[self.pomodoro.current_state()])
499
500 def change_timer_menu_item_label(self, next_label):
501 label = self.timer_item.child
502@@ -187,12 +200,12 @@
503 self.redraw_menu()
504
505 def pause(self, widget, data=None):
506- self.start_timer()
507- self.pomodoro.start()
508+ self.stop_timer()
509+ self.pomodoro.pause()
510 self.redraw_menu()
511
512 def resume(self, widget, data=None):
513- self.stop_timer()
514+ self.start_timer()
515 self.pomodoro.resume()
516 self.redraw_menu()
517
518@@ -205,7 +218,8 @@
519 self.timer_id = gobject.timeout_add(1000, self.update_timer)
520
521 def stop_timer(self):
522- gobject.source_remove(self.timer_id)
523+ if self.timer_id != None:
524+ gobject.source_remove(self.timer_id)
525 self.timer_id = None
526
527 def main(self):
528@@ -216,3 +230,4 @@
529
530 if __name__ == "__main__":
531 print __doc__
532+
533
534=== modified file 'setup.py'
535--- setup.py 2011-08-30 00:04:20 +0000
536+++ setup.py 2011-09-24 16:35:25 +0000
537@@ -31,14 +31,62 @@
538 python-gobject 2.28.3-1ubuntu1.1
539 python-notify 0.1.1-2build4
540 python-gtk2-dev 0.1.1-2build4
541+
542 """
543
544
545-import os
546+import os, sys
547+import msgfmt
548+
549+import gettext
550+# hack to get locale base dir of machine
551+BASE_LOCALE_DIR=gettext._default_localedir
552
553 from distutils.command.install import install
554+from distutils.command.build import build
555 from distutils.core import setup
556
557+from glob import glob
558+
559+PROJECTNAME='pomodoro-indicator'
560+
561+def list_message_files(suffix=".po"):
562+ """Return list of all found message files and their intallation paths"""
563+ _files = glob("po/*" + suffix)
564+ _list = []
565+ for _file in _files:
566+ # basename (without extension) is a locale name
567+ _locale = os.path.splitext(os.path.basename(_file))[0]
568+ _list.append((_file, os.path.join(
569+ _locale, "LC_MESSAGES", "%s.mo" % PROJECTNAME)))
570+ return _list
571+
572+def check_manifest():
573+ """Check that the files listed in the MANIFEST are present when the
574+ source is unpacked.
575+ """
576+ try:
577+ f = open('MANIFEST')
578+ except:
579+ print '\n*** SOURCE WARNING: The MANIFEST file is missing!'
580+ return
581+ try:
582+ manifest = [l.strip() for l in f.readlines() if l.strip().find('#') != 0]
583+ finally:
584+ f.close()
585+ err = [line for line in manifest if not os.path.exists(line)]
586+ err.sort()
587+ # ignore auto-generated files
588+ if err == ['roundup-admin', 'roundup-demo', 'roundup-gettext',
589+ 'roundup-mailgw', 'roundup-server']:
590+ err = []
591+ if err:
592+ n = len(manifest)
593+ print '\n*** SOURCE WARNING: There are files missing (%d/%d found)!'%(
594+ n-len(err), n)
595+ print 'Missing:', '\nMissing: '.join(err)
596+
597+
598
599 class CustomInstall(install):
600 """Custom installation class on package files.
601@@ -81,22 +129,52 @@
602 # save this custom data dir to later change the scripts
603 self._custom_data_dir = data_dir
604
605-
606-setup(
607- name = 'pomodoro-indicator',
608- version = '0.0.1',
609- license = 'GPL-3',
610- author = 'Marcos Vanetta',
611- author_email = 'marcosvanetta@gmail.com',
612- description = 'Pomodoro technique app indicator.',
613- long_description = 'Pomodoro technique app indicator',
614- url = 'https://github.com/malev/pomodoro-indicator',
615-
616- packages = ["pomodoro"],
617- package_data = {"pomodoro": ["images/*.png"]},
618- scripts = ["bin/pomodoro-indicator"],
619-
620- cmdclass = {
621- 'install': CustomInstall,
622- }
623-)
624+class BuildProject(build):
625+
626+ def build_message_files(self):
627+ """For each po/*.po, build .mo file in target locale directory"""
628+ for (_src, _dst) in list_message_files():
629+ _build_dst = os.path.join("build", 'locale', _dst)
630+ self.mkpath(os.path.dirname(_build_dst))
631+ self.announce("Compiling %s -> %s" % (_src, _build_dst))
632+ msgfmt.make(_src, _build_dst)
633+
634+ def run(self):
635+ check_manifest()
636+ self.build_message_files()
637+ build.run(self)
638+
639+
640+def main():
641+
642+ installdatafiles = []
643+ # add message files
644+ for (_dist_file, _mo_file) in list_message_files():
645+ realdir = os.path.split(os.path.dirname(_mo_file))
646+ installdatafiles.append((os.path.join(BASE_LOCALE_DIR, *realdir),
647+ [os.path.join("build", "locale", _mo_file)]))
648+
649+ setup(
650+ name = PROJECTNAME,
651+ version = '0.0.1',
652+ license = 'GPL-3',
653+ author = 'Marcos Vanetta',
654+ author_email = 'marcosvanetta@gmail.com',
655+ description = 'Pomodoro technique app indicator.',
656+ long_description = 'Pomodoro technique app indicator',
657+ url = 'https://github.com/malev/pomodoro-indicator',
658+
659+ packages = ["pomodoro"],
660+ package_data = {"pomodoro": ["images/*.png", ]},
661+ data_files = installdatafiles,
662+ scripts = ["bin/pomodoro-indicator"],
663+
664+ cmdclass = {
665+ 'install': CustomInstall,
666+ 'build': BuildProject
667+ }
668+ )
669+
670+if __name__ == '__main__':
671+ sys.exit(main())
672+

Subscribers

People subscribed via source and target branches

to all changes: