Merge lp:~dholbach/help-app/1425010 into lp:~ubuntu-touch-coreapps-drivers/help-app/trunk
- 1425010
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
David Planella | Needs Fixing | ||
Review via email:
|
Commit message
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.
- 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
- 96. By Daniel Holbach
-
rewrite links also for the case of en_US

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.

Daniel Holbach (dholbach) wrote : | # |
I filed bug 1425924.

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. ...
Preview Diff
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) |
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)