Merge lp:~dholbach/help-app/1425010 into lp:~ubuntu-touch-coreapps-drivers/help-app/trunk

Proposed by Daniel Holbach
Status: Merged
Merged at revision: 54
Proposed branch: lp:~dholbach/help-app/1425010
Merge into: lp:~ubuntu-touch-coreapps-drivers/help-app/trunk
Diff against target: 427 lines (+109/-125)
11 files modified
.bzrignore (+2/-1)
Makefile (+0/-1)
edit-here/Makefile (+1/-0)
edit-here/content/pages/apps.md (+0/-1)
edit-here/content/pages/faq.md (+0/-1)
edit-here/content/pages/get-in-touch.md (+0/-1)
edit-here/content/pages/index.md (+0/-1)
edit-here/generate-pot (+0/-2)
edit-here/pelicanconf.py (+6/-4)
edit-here/po/de.po (+3/-3)
edit-here/translations.py (+97/-110)
To merge this branch: bzr merge lp:~dholbach/help-app/1425010
Reviewer Review Type Date Requested Status
Daniel Holbach (community) Approve
David Planella Needs Fixing
Review via email: mp+250798@code.launchpad.net

Description of the change

This is not a complete fix for bug 1425010, but is where I got stuck for now. Links are broken again.

To post a comment you must log in.
lp:~dholbach/help-app/1425010 updated
83. By Daniel Holbach

add code for adding a fake en-US profile - write translated files to pages/<lang>/<file.md>

84. By Daniel Holbach

define location of .pot file just once

85. By Daniel Holbach

rewrite links again, we are going to go with the solution of <filename>.<lang>.html, change paths accordingly

86. By Daniel Holbach

remove stray print statements

87. By Daniel Holbach

don't generate pointless duplicates of our pages, the 'original' is the 'en-us' variant

88. By Daniel Holbach

make pep8 happy

89. By Daniel Holbach

update .bzrignore list

90. By Daniel Holbach

remove 'lang' from markdown files and its special-casing, we get it for free from the generated filenames - we can likely remove more code now

91. By Daniel Holbach

remove unused code

92. By Daniel Holbach

simplify code some more

93. By Daniel Holbach

simplify Makefile logic ('make html' will work in top and 'edit-here' directory)

94. By Daniel Holbach

restructure functions in the translations classes to make more sense

95. By Daniel Holbach

make pep8 happy

Revision history for this message
David Planella (dpm) wrote :

Looks good to me, although as discussed yesterday on IRC, I'm still not sure about creating the fake en_US translation.

For the broken links, it seems what is happening is that during the build pelican is trying to find e.g. faq.html, which is not built (we build faq.en-us.html instead) so it gets confused and outputs the link as './'. If the index.md file is modified as below, then the generated links work (but for English only).

[Take me to the FAQ!]({filename}faq.en-us.md)

review: Needs Fixing
lp:~dholbach/help-app/1425010 updated
96. By Daniel Holbach

rewrite links also for the case of en_US

Revision history for this message
David Planella (dpm) wrote :

This works well now. I've noticed something else, not sure if it needs to be fixed in this branch, though:

Whenever I click on the top-level 'de' links to switch to the German translation, the German pages contain an additional 'en' link that does not work. I think it should just be 'de' also for the German pages.

Translations: en-us en

Going forward, once we can load translations based on the user language, I'd recommend removing the translation links - languages should be set automatically for a smooth experience.

Revision history for this message
Daniel Holbach (dholbach) wrote :

I filed bug 1425924.

Revision history for this message
Daniel Holbach (dholbach) wrote :

<dholbach> dpm, the links were added automagically by pelican - I'll see what we can do and file a separate bug if that's ok
 dpm, I think we're now at the point where we can start adding content, fixing the styling and everything else
 dpm, shall I fix it in the same branch or in a separate branch?
<dpm> dholbach, yeah, let's do a separate branch
<dholbach> ok cool
 I'll land the other one, ok?
<dpm> dholbach, yeah. ...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2015-02-20 16:06:22 +0000
3+++ .bzrignore 2015-02-26 11:59:44 +0000
4@@ -1,5 +1,6 @@
5 app/www
6 *.click
7 edit-here/cache
8-edit-here/content/pages/lang-??-*
9 edit-here/backup
10+edit-here/po/en_US.po
11+edit-here/content/pages/*.*.md
12
13=== modified file 'Makefile'
14--- Makefile 2015-02-23 10:21:42 +0000
15+++ Makefile 2015-02-26 11:59:44 +0000
16@@ -11,7 +11,6 @@
17 cd app && click build . && mv *.click ..
18
19 html: clean
20- cd edit-here && ./generate-translations
21 make -C edit-here html
22
23 serve:
24
25=== modified file 'edit-here/Makefile'
26--- edit-here/Makefile 2015-02-13 16:08:50 +0000
27+++ edit-here/Makefile 2015-02-26 11:59:44 +0000
28@@ -58,6 +58,7 @@
29 @echo ' '
30
31 html:
32+ ./generate-translations
33 $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
34
35 clean:
36
37=== modified file 'edit-here/content/pages/apps.md'
38--- edit-here/content/pages/apps.md 2015-02-20 16:05:55 +0000
39+++ edit-here/content/pages/apps.md 2015-02-26 11:59:44 +0000
40@@ -1,5 +1,4 @@
41 Title: FAQ - Apps
42-Lang: en
43
44 Are you looking for a good way to do X and can't find it in the app
45 store? Want some suggestions to solve a particular problem?
46
47=== modified file 'edit-here/content/pages/faq.md'
48--- edit-here/content/pages/faq.md 2015-02-20 16:05:55 +0000
49+++ edit-here/content/pages/faq.md 2015-02-26 11:59:44 +0000
50@@ -1,5 +1,4 @@
51 Title: Get your questions answered.
52-Lang: en
53
54 We divided the questions up into categories, so you can spot yours more
55 easily. Here we go:
56
57=== modified file 'edit-here/content/pages/get-in-touch.md'
58--- edit-here/content/pages/get-in-touch.md 2015-02-20 16:05:55 +0000
59+++ edit-here/content/pages/get-in-touch.md 2015-02-26 11:59:44 +0000
60@@ -1,5 +1,4 @@
61 Title: Get in touch
62-Lang: en
63
64 Ubuntu has a huge community. You can easily get in touch with experts and
65 other enthusiasts. One good way to do so, particularly if you **have
66
67=== modified file 'edit-here/content/pages/index.md'
68--- edit-here/content/pages/index.md 2015-02-20 16:05:55 +0000
69+++ edit-here/content/pages/index.md 2015-02-26 11:59:44 +0000
70@@ -1,5 +1,4 @@
71 Title: Welcome to Help for Ubuntu for devices!
72-Lang: en
73
74 The world-wide Ubuntu community wants to give you the best possible
75 experience on your Ubuntu device. This is why we collected:
76
77=== modified file 'edit-here/generate-pot'
78--- edit-here/generate-pot 2015-02-24 09:17:31 +0000
79+++ edit-here/generate-pot 2015-02-26 11:59:44 +0000
80@@ -10,8 +10,6 @@
81
82 def main():
83 translations = Translations()
84- if not translations.clean_documents():
85- sys.exit(1)
86 translations.generate_pot_file()
87 if not translations.update_po_files():
88 sys.exit(1)
89
90=== modified file 'edit-here/pelicanconf.py'
91--- edit-here/pelicanconf.py 2015-02-24 09:35:13 +0000
92+++ edit-here/pelicanconf.py 2015-02-26 11:59:44 +0000
93@@ -14,11 +14,13 @@
94
95 RELATIVE_URLS = True
96 DIRECT_TEMPLATES = []
97+
98 SLUGIFY_SOURCE = 'basename'
99-PAGE_URL = '{slug}.html'
100-PAGE_SAVE_AS = '{slug}.html'
101-PAGE_LANG_URL = '{slug}.html'
102-PAGE_LANG_SAVE_AS = '{slug}.html'
103+FILENAME_METADATA = '(?P<slug>\S+)\.(?P<lang>\S+)\.*'
104+PAGE_URL = ''
105+PAGE_SAVE_AS = ''
106+PAGE_LANG_URL = '{slug}.{lang}.html'
107+PAGE_LANG_SAVE_AS = '{slug}.{lang}.html'
108
109 # Feed generation is usually not desired when developing
110 FEED_ALL_ATOM = None
111
112=== modified file 'edit-here/po/de.po'
113--- edit-here/po/de.po 2015-02-24 13:17:06 +0000
114+++ edit-here/po/de.po 2015-02-26 11:59:44 +0000
115@@ -33,7 +33,7 @@
116 #. type: Bullet: ' * '
117 #: content/pages/faq.md:7
118 msgid "[Apps]({filename}apps.md)"
119-msgstr "[Apps]({filename}lang-de-apps.md)"
120+msgstr "[Apps]({filename}apps.de.md)"
121
122 #. type: Bullet: ' * '
123 #: content/pages/faq.md:7
124@@ -81,14 +81,14 @@
125 #, fuzzy, no-wrap
126 #| msgid "[Take me to the FAQ!](faq.html) \n"
127 msgid "[Take me to the FAQ!]({filename}faq.md) \n"
128-msgstr "[Zu den Antworten!]({filename}lang-de-faq.md) \n"
129+msgstr "[Zu den Antworten!]({filename}faq.de.md) \n"
130
131 #. type: Plain text
132 #: content/pages/index.md:14
133 #, fuzzy
134 #| msgid "[Get in touch](get-in-touch.html)"
135 msgid "[Get in touch]({filename}get-in-touch.md)"
136-msgstr "[Kontakt]({filename}lang-de-get-in-touch.md)"
137+msgstr "[Kontakt]({filename}get-in-touch.de.md)"
138
139 #. type: Plain text
140 #: content/pages/apps.md:2
141
142=== modified file 'edit-here/translations.py'
143--- edit-here/translations.py 2015-02-24 13:16:43 +0000
144+++ edit-here/translations.py 2015-02-26 11:59:44 +0000
145@@ -6,44 +6,35 @@
146 import re
147 import shutil
148 import subprocess
149-import tempfile
150
151 from pelicanconf import PATH
152
153-METADATA_TAGS = [
154- 'title',
155- 'lang',
156- 'date',
157- ]
158-
159 # This defines how complete we expect translations to be before we
160 # generate HTML from them. Needs to be string.
161 TRANSLATION_COMPLETION_PERCENTAGE = '0'
162
163+BCP47_OVERRIDES = (
164+ ('zh_CN', 'zh-hans'),
165+ ('zh_TW', 'zh-hant'),
166+)
167+
168
169 class PO4A(object):
170- def __init__(self, translations_dir, temp_dir):
171+ def __init__(self, pot_file):
172 self.default_args = [
173 '-f', 'text',
174 '-o', 'markdown',
175 '-M', 'utf-8',
176 ]
177- self.translations_dir = translations_dir
178- self.temp_dir = temp_dir
179+ self.pot_file = pot_file
180
181- def run(self, po4a_command, additional_args, with_output=False,
182- working_dir=None):
183- if not working_dir:
184- working_dir = self.temp_dir
185- pwd = os.getcwd()
186+ def run(self, po4a_command, additional_args, with_output=False):
187 args = copy.copy(self.default_args)
188 args += additional_args
189- os.chdir(working_dir)
190 if with_output:
191 ret = subprocess.Popen([po4a_command]+args, stdout=subprocess.PIPE)
192 else:
193 ret = subprocess.call([po4a_command]+args)
194- os.chdir(pwd)
195 return ret
196
197 def gettextize(self, documents):
198@@ -51,7 +42,7 @@
199 for document in documents:
200 args += ['-m', document]
201 args += [
202- '-p', os.path.join(self.translations_dir, 'help.pot'),
203+ '-p', self.pot_file,
204 '-L', 'utf-8',
205 ]
206 return self.run('po4a-gettextize', args)
207@@ -74,29 +65,80 @@
208 '-p', lang.file_name,
209 '-L', 'utf-8',
210 ]
211- return self.run('po4a-translate', args, with_output=True,
212- working_dir=os.path.join(PATH, '..'))
213+ return self.run('po4a-translate', args, with_output=True)
214+
215+
216+class Documents(object):
217+ def __init__(self):
218+ self.docs = []
219+ for dirpath, dirnames, filenames in os.walk(PATH):
220+ for filename in filenames:
221+ self.docs += [os.path.join(dirpath, filename)]
222+
223+ def translated_doc(self, file_name, lang):
224+ match = [doc for doc in self.docs
225+ if os.path.basename(doc) == os.path.basename(file_name)]
226+ if not match:
227+ return None
228+ return '%s.%s.md' % (match[0].split('.md')[0],
229+ lang.bcp47_code)
230+
231+ def _call_po4a_translate(self, doc, lang, po4a):
232+ res = po4a.translate(doc, lang)
233+ output = codecs.decode(res.communicate()[0])
234+ broken_title_line = [line for line in output.split('\n')
235+ if line.lower().startswith('title:')][0]
236+ rest = [line for line in output.split('\n')
237+ if not line.lower().startswith('title')]
238+ output = '\n'.join(rest)
239+ return (broken_title_line, output)
240+
241+ def write_translated_markdown(self, lang, po4a):
242+ for doc in self.docs:
243+ (broken_title_line, output) = \
244+ self._call_po4a_translate(doc, lang, po4a)
245+ new_path = self.translated_doc(doc, lang)
246+ text = "%s\nDate:\n\n" % (broken_title_line)
247+ text += output
248+ if os.path.exists(new_path):
249+ os.remove(new_path)
250+ if not os.path.exists(os.path.dirname(new_path)):
251+ os.makedirs(os.path.dirname(new_path))
252+ with open(new_path, 'w', encoding='utf-8') as f:
253+ f.write(text)
254
255
256 class Language(object):
257- def __init__(self, po_file):
258+ def __init__(self, po_file, documents):
259 self.file_name = po_file
260- self.gettext_lang_code = os.path.basename(po_file).split('.po')[0]
261+ self.gettext_code = os.path.basename(po_file).split('.po')[0]
262+ self.bcp47_code = self._find_bcp47_code()
263+ self.documents = documents
264+
265+ def _find_bcp47_code(self):
266+ if self.gettext_code not in [c[0] for c in BCP47_OVERRIDES]:
267+ return self.gettext_code.lower().replace('_', '-')
268+ return [c[1] for c in BCP47_OVERRIDES
269+ if c[0] == self.gettext_code][0]
270
271 def rewrite_links(self):
272 po_file = polib.pofile(self.file_name)
273- for entry_group in [po_file.untranslated_entries(),
274- po_file.translated_entries(),
275- po_file.fuzzy_entries()]:
276+ link_regex = r'\[.+?\]\(\{filename\}(.+?)\)'
277+ for entry_group in [po_file.translated_entries(),
278+ po_file.fuzzy_entries(),
279+ po_file.untranslated_entries()]:
280 for entry in entry_group:
281 if '{filename}' in entry.msgid:
282- if not entry.msgstr:
283+ link_msgid = re.findall(link_regex, entry.msgid)[0]
284+ link_msgstr = list(re.findall(link_regex, entry.msgstr))
285+ translated_doc = os.path.basename(
286+ self.documents.translated_doc(
287+ link_msgid, self))
288+ if not link_msgstr:
289 entry.msgstr = entry.msgid
290- link = re.findall(r'\[.+?\]\(\{filename\}(.+?)\)',
291- entry.msgid)[0]
292- entry.msgstr = entry.msgstr.replace(
293- link,
294- 'lang-%s-%s' % (self.gettext_lang_code, link))
295+ link_msgstr = [link_msgid]
296+ entry.msgstr = entry.msgstr.replace(link_msgstr[0],
297+ translated_doc)
298 po_file.save(self.file_name)
299
300
301@@ -105,21 +147,19 @@
302 self._cleanup()
303 self.translations_dir = os.path.abspath(os.path.join(PATH, '../po'))
304 self.available_languages = []
305+ self.documents = Documents()
306 for po_filename in glob.glob(self.translations_dir+'/*.po'):
307- self.available_languages += [Language(po_filename)]
308- self.documents = self._find_documents()
309- self.temp_dir = tempfile.mkdtemp()
310- self.po4a = PO4A(self.translations_dir, self.temp_dir)
311+ self.available_languages += [Language(po_filename, self.documents)]
312+ self.fake_lang_code = 'en_US'
313+ self.fake_po_file = os.path.join(self.translations_dir,
314+ '%s.po' % self.fake_lang_code)
315+ self.pot_file = os.path.join(self.translations_dir,
316+ "help.pot")
317+ self.po4a = PO4A(self.pot_file)
318
319 def __del__(self):
320- shutil.rmtree(self.temp_dir)
321-
322- def _find_documents(self):
323- documents = []
324- for dirpath, dirnames, filenames in os.walk(PATH):
325- for filename in filenames:
326- documents += [os.path.join(dirpath, filename)]
327- return documents
328+ if os.path.exists(self.fake_po_file):
329+ os.remove(self.fake_po_file)
330
331 def _cleanup(self):
332 r = subprocess.Popen(['bzr', 'ignored'], stdout=subprocess.PIPE)
333@@ -133,80 +173,27 @@
334 except NotADirectoryError:
335 os.remove(f)
336
337- def _remove_metadata(self, filename):
338- new_dir = os.path.join(self.temp_dir, os.path.dirname(filename))
339- os.makedirs(new_dir, exist_ok=True)
340- new_filename = os.path.join(new_dir, os.path.basename(filename))
341- shutil.copy(filename, new_filename)
342- lines = open(new_filename).readlines()
343- title_line = [x for x in lines if
344- (x.startswith('Title:') or x.startswith('title:'))]
345- index = 0
346- for line in lines:
347- if not [x for x in METADATA_TAGS
348- if line.lower().startswith(x+':')] and \
349- line.strip() != '':
350- index = lines.index(line)
351- break
352- if not title_line:
353- print('No line starting with "Title: " found in "%s".' %
354- new_filename)
355- return False
356- os.remove(new_filename)
357- with open(new_filename, 'w', encoding='utf-8') as new_file:
358- new_file.write(title_line[0]+'\n')
359- new_file.write(''.join(lines[index:]))
360- return True
361-
362- def clean_documents(self):
363- for document in self.documents:
364- if not self._remove_metadata(document):
365- return False
366- return True
367-
368 def generate_pot_file(self):
369- return self.po4a.gettextize(self.documents)
370+ return self.po4a.gettextize(self.documents.docs)
371
372 def update_po_files(self):
373- return self.po4a.updatepo(self.available_languages, self.documents)
374-
375- def _call_po4a_translate(self, doc, lang):
376- res = self.po4a.translate(doc, lang)
377- output = codecs.decode(res.communicate()[0])
378- broken_title_line = [line for line in output.split('\n')
379- if line.lower().startswith('title:')][0]
380- rest = [line for line in output.split('\n')
381- if not line.lower().startswith('title')]
382- output = '\n'.join(rest)
383- return (broken_title_line, output)
384-
385- def _new_header(self, lang, broken_title_line):
386- title_line = broken_title_line
387- for metadata_tag in [x for x in METADATA_TAGS if not x == 'title']:
388- title_line = title_line.split(metadata_tag)[0]
389- title_line = title_line.split(metadata_tag.capitalize())[0]
390- title_line = title_line.split(metadata_tag.upper())[0]
391- return "%s\nLang: %s\nDate:\n\n" % \
392- (title_line, lang.gettext_lang_code)
393+ return self.po4a.updatepo(self.available_languages,
394+ self.documents.docs)
395
396 def rewrite_links(self):
397+ self._generate_fake_pofile()
398 for lang in self.available_languages:
399 lang.rewrite_links()
400
401+ # we generate a fake translation for en-US which is going to be
402+ # the default
403+ def _generate_fake_pofile(self):
404+ if os.path.exists(self.fake_po_file):
405+ os.remove(self.fake_po_file)
406+ shutil.copy(self.pot_file, self.fake_po_file)
407+ self.available_languages += [Language(self.fake_po_file,
408+ self.documents)]
409+
410 def generate_translations(self):
411 for lang in self.available_languages:
412- for doc in self.documents:
413- (broken_title_line, output) = \
414- self._call_po4a_translate(doc, lang)
415- new_path = os.path.join(PATH, 'pages',
416- 'lang-%s-%s' %
417- (lang.gettext_lang_code,
418- os.path.basename(doc)))
419- text = self._new_header(lang, broken_title_line)
420- text += output
421- if os.path.exists(new_path):
422- os.remove(new_path)
423- if not os.path.exists(os.path.dirname(new_path)):
424- os.makedirs(os.path.dirname(new_path))
425- with open(new_path, 'w', encoding='utf-8') as f:
426- f.write(text)
427+ self.documents.write_translated_markdown(lang, self.po4a)

Subscribers

People subscribed via source and target branches