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

Proposed by Daniel Holbach
Status: Merged
Merged at revision: 115
Proposed branch: lp:~dholbach/help-app/1429896
Merge into: lp:~ubuntu-touch-coreapps-drivers/help-app/trunk
Diff against target: 2553 lines (+697/-877)
18 files modified
.bzrignore (+5/-6)
HACKING (+26/-24)
Makefile (+54/-106)
debian/help-app-web.docs (+1/-1)
edit-here/develop_server.sh (+0/-103)
edit-here/fabfile.py (+0/-73)
edit-here/publishconf.py (+0/-24)
internals/generate-pot (+1/-1)
internals/generate-translations (+1/-1)
internals/pelicanconf.py (+9/-7)
internals/run-tests (+4/-1)
internals/tests/test_files.py (+8/-3)
internals/tests/test_links.py (+6/-2)
internals/tests/test_translations.py (+2/-2)
internals/translations/build.py (+304/-0)
internals/translations/po4a.py (+62/-0)
internals/translations/utils.py (+19/-326)
po/help.pot (+195/-197)
To merge this branch: bzr merge lp:~dholbach/help-app/1429896
Reviewer Review Type Date Requested Status
Nicholas Skaggs (community) Approve
Review via email: mp+253518@code.launchpad.net

Commit message

.
├── content
│   ├── images
│   └── pages
├── debian
│   └── source
├── internals
│   ├── tests
│   └── translations
├── po
└── static
    ├── app
    └── themes
        ├── app
        │   ├── static
        │   │   ├── css
        │   │   └── js
        │   └── templates
        └── web
            ├── static
            │   ├── css
            │   ├── img
            │   └── js
            │   └── plugins
            └── templates

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

remove some superfluous code

120. By Daniel Holbach

break po4a bits out into separate module

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Directory structure above needs to be added to the FAQ. Also update the other sections, including testings and generating content.

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

update HACKING doc

122. By Daniel Holbach

update packaging to use proper build path

123. By Daniel Holbach

fix cleanup

124. By Daniel Holbach

requiring the 'clean' target to be run before is unnecessary - it's done as part of instantiating Translations anyway

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

If you could let me know how to reproduce this, that'd be great. :)

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Apart from it not building on my box, I'm happy with this merge. Now to solve that pesky not building thing . . .

lp:~dholbach/help-app/1429896 updated
125. By Daniel Holbach

normalise path of fake po file

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Yay! This looks wonderful.

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-03-18 06:57:45 +0000
3+++ .bzrignore 2015-03-19 17:24:01 +0000
4@@ -1,8 +1,7 @@
5-app/www
6+build/
7 *.click
8 *.user
9-edit-here/cache
10-edit-here/backup
11-edit-here/po/en_US.po
12-edit-here/content/pages/*.*.md
13-web/
14+cache
15+backup
16+po/en_US.po
17+content/pages/*.*.md
18
19=== modified file 'HACKING'
20--- HACKING 2015-03-18 10:37:42 +0000
21+++ HACKING 2015-03-19 17:24:01 +0000
22@@ -50,22 +50,24 @@
23 Directory structure
24 -------------------
25
26-
27-├── app ← Everything related to the devices app.
28-│   └── www ← The viewable content of the app.
29-├── edit-here
30-│   ├── content
31-│   │   └── pages ← Here is the place to edit the content.
32-│   ├── po ← Translations.
33-│   ├── tests ← Code for automated testing goes here.
34-│  └── themes ← Themes files, both templates and css/js.
35-│      ├── phone ← Phone/device app theme.
36-│      └── web ← Online build (.ubuntu.com) theme.
37-└── web ← The viewable content of of the online build.
38-
39-Note: Everything in ./app/www and ./web/ is automatically generated.
40- Edit content in ./edit-here/content/pages/ and edit themes in
41- ./edit-here/themes/.
42+.
43+├── content ← Here is the place to edit the content.
44+│ ├── images ← Images to be used in the docs.
45+│ └── pages ← Actual text to be edited.
46+├── debian ← Everything related to .deb packaging.
47+├── internals ← Build tools, pelican config.
48+│ ├── tests ← Code for automated testing.
49+│ └── translations ← Python code for handling translations.
50+├── po ← Translations (.pot and po files).
51+└── static
52+ ├── app ← Static files for the click app.
53+ └── themes ← Themes files, both templates and css/js.
54+ ├── app
55+ └── web
56+
57+Note: Everything in .build is automatically generated.
58+ Edit content in ./content/pages/ and edit themes in
59+ ./static/themes/.
60
61
62 Prerequisites
63@@ -96,7 +98,7 @@
64 This app is structured in a very simple way. All the content is and all your
65 edits happen in
66
67- ./edit-here/content/pages/
68+ ./content/pages/
69
70 The markup for the text is Markdown, which is very easy to learn. Just have
71 a look at http://daringfireball.net/projects/markdown/ and some of the
72@@ -104,18 +106,18 @@
73
74 Once you're happy with your edits, change to the top-level directory and run
75
76- make html
77+ make app
78
79 This will also generate translated pages.
80
81-You can find the updated HTML in ./app/www/.
82+You can find the updated HTML in ./build/app/www/.
83
84 To launch the app, you can use ubuntu-html5-app-launcher in the www dir, or
85 just call
86
87 make launch
88
89-(This will also run the 'make html' command for you.)
90+(This will also run the 'make app' command for you.)
91
92
93 Creating a click
94@@ -137,7 +139,7 @@
95
96 make web
97
98-Find the resulting files in ./web in the top-level directory.
99+Find the resulting files in the ./build/web directory.
100
101 We plan to make use of
102 http://www.w3.org/International/questions/qa-apache-lang-neg for publishing
103@@ -148,7 +150,7 @@
104
105 We love automated testing! We added a couple of test cases to
106
107- ./edit-here/tests/
108+ ./internals/tests/
109
110 and it would be great if you added more. We want to run these tests
111 whenever we build the package or publish the docs or whatever else. Just
112@@ -162,7 +164,7 @@
113 While you can just generate and install the click package manually,
114 we recommend the SDK for testing the phone app.
115
116-You can run the `make html` target to generate the HTML files and then
117+You can run the `make appl` target to generate the HTML files and then
118 open the app/help.ubuntuhtmlproject file with the Ubuntu SDK IDE.
119
120 From there, you can set up a desktop kit to run it on your host,
121@@ -177,7 +179,7 @@
122
123 https://launchpad.net/help-app
124
125-To update the .pot file, simply run the following command in the edit-here/
126+To update the .pot file, simply run the following command in the top-level
127 directory:
128
129 make update-pot
130
131=== removed file 'Makefile'
132--- Makefile 2015-03-18 10:36:10 +0000
133+++ Makefile 1970-01-01 00:00:00 +0000
134@@ -1,28 +0,0 @@
135-#!/usr/bin/make -f
136-
137-ignored := $(shell bzr ignored | cut -d' ' -f1)
138-
139-clean:
140-ifneq ($(strip $(ignored)),)
141- $(foreach fn, $(ignored), $(shell rm -r $(fn);))
142-endif
143-
144-check: clean
145- make -C edit-here check
146-
147-click: html
148- cd app && click build . && mv *.click ..
149-
150-html: clean
151- make -C edit-here html
152-
153-web: clean
154- make -C edit-here web
155-
156-update-pot:
157- cd edit-here && ./generate-pot
158-
159-launch:
160- make -C edit-here launch
161-
162-.PHONY: click html web update-pot clean check
163
164=== renamed file 'edit-here/Makefile' => 'Makefile'
165--- edit-here/Makefile 2015-03-18 10:36:10 +0000
166+++ Makefile 2015-03-19 17:24:01 +0000
167@@ -4,126 +4,74 @@
168
169 BASEDIR=$(CURDIR)
170 INPUTDIR=$(BASEDIR)/content
171-OUTPUTDIR=$(BASEDIR)/../app/www
172-CONFFILE=$(BASEDIR)/pelicanconf.py
173-PUBLISHCONF=$(BASEDIR)/publishconf.py
174-
175-OUTPUTDIR_WEB=$(BASEDIR)/../web
176-THEMEDIR_WEB=$(BASEDIR)/themes/web
177-
178-FTP_HOST=localhost
179-FTP_USER=anonymous
180-FTP_TARGET_DIR=/
181-
182-SSH_HOST=localhost
183-SSH_PORT=22
184-SSH_USER=root
185-SSH_TARGET_DIR=/var/www
186-
187-S3_BUCKET=my_s3_bucket
188-
189-CLOUDFILES_USERNAME=my_rackspace_username
190-CLOUDFILES_API_KEY=my_rackspace_api_key
191-CLOUDFILES_CONTAINER=my_cloudfiles_container
192-
193-DROPBOX_DIR=~/Dropbox/Public/
194-
195-GITHUB_PAGES_BRANCH=gh-pages
196+INTERNALS_DIR=$(BASEDIR)/internals
197+PO_DIR=$(BASEDIR)/po
198+STATIC_DIR=$(BASEDIR)/static
199+
200+CONFFILE=$(INTERNALS_DIR)/pelicanconf.py
201+
202+BUILD_DIR=$(BASEDIR)/build
203+APP_DIR=$(BUILD_DIR)/app
204+OUTPUTDIR_APP=$(APP_DIR)/www
205+
206+OUTPUTDIR_WEB=$(BUILD_DIR)/web/www
207+THEMEDIR_WEB=$(STATIC_DIR)/themes/web
208
209 DEBUG ?= 0
210 ifeq ($(DEBUG), 1)
211 PELICANOPTS += -D
212 endif
213
214-MD_FILES=$(wildcard content/pages/*.md)
215-PO_FILES=$(wildcard po/*.po)
216+MD_FILES=$(wildcard $(INPUTDIR)/pages/*.md)
217+PO_FILES=$(wildcard $(PO_DIR)/*.po)
218+
219+ignored := $(shell bzr ignored | cut -d' ' -f1)
220+
221+clean:
222+ifneq ($(strip $(ignored)),)
223+ $(foreach fn, $(ignored), $(shell rm -r $(fn);))
224+endif
225
226 help:
227 @echo 'Makefile for a pelican Web site '
228 @echo ' '
229 @echo 'Usage: '
230- @echo ' make html (re)generate the web site '
231+ @echo ' make web (re)generate the (online) web site '
232+ @echo ' make app (re)generate the (offline) content '
233+ @echo ' for the phone app ("html" is an '
234+ @echo ' alias) '
235+ @echo ' make click generate click for the phone app '
236+ @echo ' make launch launch the phone app '
237+ @echo ' make translations generate translated markdown '
238+ @echo ' make update-pot update the .pot file '
239 @echo ' make clean remove the generated files '
240- @echo ' make regenerate regenerate files upon modification '
241- @echo ' make publish generate using production settings '
242- @echo ' make serve [PORT=8000] serve site at http://localhost:8000'
243- @echo ' make devserver [PORT=8000] start/restart develop_server.sh '
244- @echo ' make stopserver stop local server '
245- @echo ' make ssh_upload upload the web site via SSH '
246- @echo ' make rsync_upload upload the web site via rsync+ssh '
247- @echo ' make dropbox_upload upload the web site via Dropbox '
248- @echo ' make ftp_upload upload the web site via FTP '
249- @echo ' make s3_upload upload the web site via S3 '
250- @echo ' make cf_upload upload the web site via Cloud Files'
251- @echo ' make github upload the web site via gh-pages '
252 @echo ' '
253 @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
254 @echo ' '
255
256 check:
257- ./run-tests
258-
259-web:
260- ./generate-translations
261- $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_WEB) -s $(CONFFILE) $(PELICANOPTS) -t $(THEMEDIR_WEB)
262-
263-html:
264- ./generate-translations
265- $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
266- cp index.html $(OUTPUTDIR)
267-
268-clean:
269- [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)
270-
271-launch: html
272- cd ../app/; `grep '^Exec' help.desktop | tail -1 | sed 's/^Exec=//' | sed 's/%.//'` &
273-
274-#regenerate:
275-# $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
276-#
277-#serve:
278-#ifdef PORT
279-# cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
280-#else
281-# cd $(OUTPUTDIR) && $(PY) -m pelican.server
282-#endif
283-#
284-#devserver:
285-#ifdef PORT
286-# $(BASEDIR)/develop_server.sh restart $(PORT)
287-#else
288-# $(BASEDIR)/develop_server.sh restart
289-#endif
290-#
291-#stopserver:
292-# kill -9 `cat pelican.pid`
293-# kill -9 `cat srv.pid`
294-# @echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
295-#
296-#publish:
297-# $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
298-#
299-#ssh_upload: publish
300-# scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
301-#
302-#rsync_upload: publish
303-# rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
304-#
305-#dropbox_upload: publish
306-# cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR)
307-#
308-#ftp_upload: publish
309-# lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit"
310-#
311-#s3_upload: publish
312-# s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type
313-#
314-#cf_upload: publish
315-# cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) .
316-#
317-#github: publish
318-# ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR)
319-# git push origin $(GITHUB_PAGES_BRANCH)
320-#
321-.PHONY: html help clean web check
322- #regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github
323+ cd $(INTERNALS_DIR); ./run-tests
324+
325+translations:
326+ cd $(INTERNALS_DIR); ./generate-translations
327+
328+web: translations
329+ cd $(INTERNALS_DIR); $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_WEB) -s $(CONFFILE) $(PELICANOPTS) -t $(THEMEDIR_WEB)
330+
331+app: translations
332+ cd $(INTERNALS_DIR); $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_APP) -s $(CONFFILE) $(PELICANOPTS)
333+ cp $(STATIC_DIR)/index.html $(OUTPUTDIR_APP)
334+ cp $(STATIC_DIR)/app/* $(APP_DIR)
335+
336+html: app
337+
338+click: app
339+ cd $(APP_DIR) && click build . && mv *.click $(BASEDIR)
340+
341+launch: app
342+ cd $(APP_DIR); `grep '^Exec' help.desktop | tail -1 | sed 's/^Exec=//' | sed 's/%.//'` &
343+
344+update-pot:
345+ cd $(INTERNALS_DIR) && ./generate-pot
346+
347+.PHONY: html help clean web check launch click app update-pot translations
348
349=== renamed directory 'edit-here/content' => 'content'
350=== modified file 'debian/help-app-web.docs'
351--- debian/help-app-web.docs 2015-03-09 15:47:10 +0000
352+++ debian/help-app-web.docs 2015-03-19 17:24:01 +0000
353@@ -1,1 +1,1 @@
354-web/*
355+build/web/www/*
356
357=== removed directory 'edit-here'
358=== removed file 'edit-here/develop_server.sh'
359--- edit-here/develop_server.sh 2015-02-09 13:40:52 +0000
360+++ edit-here/develop_server.sh 1970-01-01 00:00:00 +0000
361@@ -1,103 +0,0 @@
362-#!/usr/bin/env bash
363-##
364-# This section should match your Makefile
365-##
366-PY=${PY:-python}
367-PELICAN=${PELICAN:-pelican}
368-PELICANOPTS=
369-
370-BASEDIR=$(pwd)
371-INPUTDIR=$BASEDIR/content
372-OUTPUTDIR=$(BASEDIR)/../app/www
373-CONFFILE=$BASEDIR/pelicanconf.py
374-
375-###
376-# Don't change stuff below here unless you are sure
377-###
378-
379-SRV_PID=$BASEDIR/srv.pid
380-PELICAN_PID=$BASEDIR/pelican.pid
381-
382-function usage(){
383- echo "usage: $0 (stop) (start) (restart) [port]"
384- echo "This starts Pelican in debug and reload mode and then launches"
385- echo "an HTTP server to help site development. It doesn't read"
386- echo "your Pelican settings, so if you edit any paths in your Makefile"
387- echo "you will need to edit your settings as well."
388- exit 3
389-}
390-
391-function alive() {
392- kill -0 $1 >/dev/null 2>&1
393-}
394-
395-function shut_down(){
396- PID=$(cat $SRV_PID)
397- if [[ $? -eq 0 ]]; then
398- if alive $PID; then
399- echo "Stopping HTTP server"
400- kill $PID
401- else
402- echo "Stale PID, deleting"
403- fi
404- rm $SRV_PID
405- else
406- echo "HTTP server PIDFile not found"
407- fi
408-
409- PID=$(cat $PELICAN_PID)
410- if [[ $? -eq 0 ]]; then
411- if alive $PID; then
412- echo "Killing Pelican"
413- kill $PID
414- else
415- echo "Stale PID, deleting"
416- fi
417- rm $PELICAN_PID
418- else
419- echo "Pelican PIDFile not found"
420- fi
421-}
422-
423-function start_up(){
424- local port=$1
425- echo "Starting up Pelican and HTTP server"
426- shift
427- $PELICAN --debug --autoreload -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS &
428- pelican_pid=$!
429- echo $pelican_pid > $PELICAN_PID
430- cd $OUTPUTDIR
431- $PY -m pelican.server $port &
432- srv_pid=$!
433- echo $srv_pid > $SRV_PID
434- cd $BASEDIR
435- sleep 1
436- if ! alive $pelican_pid ; then
437- echo "Pelican didn't start. Is the Pelican package installed?"
438- return 1
439- elif ! alive $srv_pid ; then
440- echo "The HTTP server didn't start. Is there another service using port" $port "?"
441- return 1
442- fi
443- echo 'Pelican and HTTP server processes now running in background.'
444-}
445-
446-###
447-# MAIN
448-###
449-[[ ($# -eq 0) || ($# -gt 2) ]] && usage
450-port=''
451-[[ $# -eq 2 ]] && port=$2
452-
453-if [[ $1 == "stop" ]]; then
454- shut_down
455-elif [[ $1 == "restart" ]]; then
456- shut_down
457- start_up $port
458-elif [[ $1 == "start" ]]; then
459- if ! start_up $port; then
460- shut_down
461- fi
462-else
463- usage
464-fi
465
466=== removed file 'edit-here/fabfile.py'
467--- edit-here/fabfile.py 2015-02-09 13:40:52 +0000
468+++ edit-here/fabfile.py 1970-01-01 00:00:00 +0000
469@@ -1,73 +0,0 @@
470-from fabric.api import *
471-import fabric.contrib.project as project
472-import os
473-import sys
474-import SimpleHTTPServer
475-import SocketServer
476-
477-# Local path configuration (can be absolute or relative to fabfile)
478-env.deploy_path = '../app/www'
479-DEPLOY_PATH = env.deploy_path
480-
481-# Remote server configuration
482-production = 'root@localhost:22'
483-dest_path = '/var/www'
484-
485-# Rackspace Cloud Files configuration settings
486-env.cloudfiles_username = 'my_rackspace_username'
487-env.cloudfiles_api_key = 'my_rackspace_api_key'
488-env.cloudfiles_container = 'my_cloudfiles_container'
489-
490-
491-def clean():
492- if os.path.isdir(DEPLOY_PATH):
493- local('rm -rf {deploy_path}'.format(**env))
494- local('mkdir {deploy_path}'.format(**env))
495-
496-def build():
497- local('pelican -s pelicanconf.py')
498-
499-def rebuild():
500- clean()
501- build()
502-
503-def regenerate():
504- local('pelican -r -s pelicanconf.py')
505-
506-def serve():
507- os.chdir(env.deploy_path)
508-
509- PORT = 8000
510- class AddressReuseTCPServer(SocketServer.TCPServer):
511- allow_reuse_address = True
512-
513- server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
514-
515- sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
516- server.serve_forever()
517-
518-def reserve():
519- build()
520- serve()
521-
522-def preview():
523- local('pelican -s publishconf.py')
524-
525-def cf_upload():
526- rebuild()
527- local('cd {deploy_path} && '
528- 'swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
529- '-U {cloudfiles_username} '
530- '-K {cloudfiles_api_key} '
531- 'upload -c {cloudfiles_container} .'.format(**env))
532-
533-@hosts(production)
534-def publish():
535- local('pelican -s publishconf.py')
536- project.rsync_project(
537- remote_dir=dest_path,
538- exclude=".DS_Store",
539- local_dir=DEPLOY_PATH.rstrip('/') + '/',
540- delete=True,
541- extra_opts='-c',
542- )
543
544=== removed directory 'edit-here/local'
545=== removed file 'edit-here/local/__init__.py'
546=== removed file 'edit-here/publishconf.py'
547--- edit-here/publishconf.py 2015-02-09 13:40:52 +0000
548+++ edit-here/publishconf.py 1970-01-01 00:00:00 +0000
549@@ -1,24 +0,0 @@
550-#!/usr/bin/env python
551-# -*- coding: utf-8 -*- #
552-from __future__ import unicode_literals
553-
554-# This file is only used if you use `make publish` or
555-# explicitly specify it as your config file.
556-
557-import os
558-import sys
559-sys.path.append(os.curdir)
560-from pelicanconf import *
561-
562-SITEURL = ''
563-RELATIVE_URLS = False
564-
565-FEED_ALL_ATOM = 'feeds/all.atom.xml'
566-CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
567-
568-DELETE_OUTPUT_DIRECTORY = True
569-
570-# Following items are often useful when publishing
571-
572-#DISQUS_SITENAME = ""
573-#GOOGLE_ANALYTICS = ""
574
575=== added directory 'internals'
576=== renamed file 'edit-here/__init__.py' => 'internals/__init__.py'
577=== renamed file 'edit-here/generate-pot' => 'internals/generate-pot'
578--- edit-here/generate-pot 2015-03-02 10:30:45 +0000
579+++ internals/generate-pot 2015-03-19 17:24:01 +0000
580@@ -5,7 +5,7 @@
581
582 import sys
583
584-from translations import Translations
585+from translations.build import Translations
586
587
588 def main():
589
590=== renamed file 'edit-here/generate-translations' => 'internals/generate-translations'
591--- edit-here/generate-translations 2015-03-02 10:30:45 +0000
592+++ internals/generate-translations 2015-03-19 17:24:01 +0000
593@@ -4,7 +4,7 @@
594
595 import sys
596
597-from translations import Translations
598+from translations.build import Translations
599
600
601 def main():
602
603=== renamed file 'edit-here/pelicanconf.py' => 'internals/pelicanconf.py'
604--- edit-here/pelicanconf.py 2015-03-17 16:40:17 +0000
605+++ internals/pelicanconf.py 2015-03-19 17:24:01 +0000
606@@ -6,7 +6,9 @@
607 SITENAME = u'Ubuntu for devices: Help'
608 SITEURL = ''
609
610-PATH = 'content'
611+TOP_LEVEL_DIR = '../'
612+PATH = TOP_LEVEL_DIR+'content/'
613+TRANSLATIONS_DIR = TOP_LEVEL_DIR+'po/'
614
615 TIMEZONE = 'Europe/Paris'
616
617@@ -17,9 +19,9 @@
618
619 SLUGIFY_SOURCE = 'basename'
620 FILENAME_METADATA = '(?P<slug>\S+)\.(?P<lang>\S+)\.*'
621-PAGE_URL = ''
622-PAGE_SAVE_AS = ''
623-PAGE_LANG_URL = '{slug}.{lang}.html'
624+PAGE_URL = ''
625+PAGE_SAVE_AS = ''
626+PAGE_LANG_URL = '{slug}.{lang}.html'
627 PAGE_LANG_SAVE_AS = '{slug}.{lang}.html'
628
629 # Feed generation is usually not desired when developing
630@@ -38,13 +40,13 @@
631 DEFAULT_PAGINATION = False
632
633 # Uncomment following line if you want document-relative URLs when developing
634-#RELATIVE_URLS = True
635+# RELATIVE_URLS = True
636
637 TAGS_SAVE_AS = ''
638 TAG_SAVE_AS = ''
639-THEME = 'themes/phone'
640+THEME = TOP_LEVEL_DIR+'static/themes/app/'
641
642-MD_EXTENSIONS = ['local.q-and-a', 'attr_list', 'toc']
643+MD_EXTENSIONS = ['q-and-a', 'attr_list', 'toc']
644
645 META_TAGS = [
646 '[TOC]',
647
648=== renamed file 'edit-here/local/q-and-a.py' => 'internals/q-and-a.py'
649=== renamed file 'edit-here/run-tests' => 'internals/run-tests'
650--- edit-here/run-tests 2015-03-09 10:16:07 +0000
651+++ internals/run-tests 2015-03-19 17:24:01 +0000
652@@ -1,9 +1,12 @@
653 #!/usr/bin/python3
654
655+import os
656 import sys
657 import unittest
658
659-test_directory = 'tests/'
660+from pelicanconf import TOP_LEVEL_DIR
661+
662+test_directory = os.path.join(TOP_LEVEL_DIR, 'internals/tests')
663 test_filename = 'test_*'
664 if len(sys.argv) > 1:
665 test_filename = sys.argv[1]
666
667=== renamed directory 'edit-here/tests' => 'internals/tests'
668=== modified file 'internals/tests/test_files.py'
669--- edit-here/tests/test_files.py 2015-03-11 11:57:50 +0000
670+++ internals/tests/test_files.py 2015-03-19 17:24:01 +0000
671@@ -1,26 +1,31 @@
672 import os
673 from unittest import TestCase
674
675-import translations
676+from translations.build import Translations
677+from translations import utils
678
679
680 class HelpTestCase(TestCase):
681 def __init__(self, *args):
682- self.translations = translations.Translations()
683+ self.translations = Translations()
684 TestCase.__init__(self, *args)
685
686 def test_doc_files_size_not_0(self):
687+ pwd = utils.use_top_level_dir()
688 sizes = [os.stat(fn).st_size
689 for fn in self.translations.documents.docs]
690 self.assertNotIn(0, sizes)
691+ os.chdir(pwd)
692
693 def test_po_files_size_not_0(self):
694+ pwd = utils.use_top_level_dir()
695 sizes = [os.stat(fn).st_size
696 for fn in self.translations.po.langs]
697 self.assertNotIn(0, sizes)
698+ os.chdir(pwd)
699
700 def test_markdown_files(self):
701 fns = self.translations.documents.find_docs()
702- checked_fns = [translations.verify_markdown_file(fn)
703+ checked_fns = [utils.verify_markdown_file(fn)
704 for fn in fns]
705 self.assertEqual(len(fns), checked_fns.count(True))
706
707=== modified file 'internals/tests/test_links.py'
708--- edit-here/tests/test_links.py 2015-03-10 17:20:47 +0000
709+++ internals/tests/test_links.py 2015-03-19 17:24:01 +0000
710@@ -7,15 +7,19 @@
711 from unittest import TestCase
712 import urllib.parse
713
714+from translations.utils import use_top_level_dir
715+
716
717 def require_build(build):
718 tempdir = tempfile.mkdtemp()
719 env = {}
720- if build == 'html':
721- env = {'OUTPUTDIR': tempdir}
722+ if build == 'app':
723+ env = {'OUTPUTDIR_APP': tempdir}
724 if build == 'web':
725 env = {'OUTPUTDIR_WEB': tempdir}
726+ pwd = use_top_level_dir()
727 ret = subprocess.call(['make', '-es', build], env=env)
728+ os.chdir(pwd)
729 return (ret, tempdir)
730
731
732
733=== modified file 'internals/tests/test_translations.py'
734--- edit-here/tests/test_translations.py 2015-03-17 09:24:48 +0000
735+++ internals/tests/test_translations.py 2015-03-19 17:24:01 +0000
736@@ -1,11 +1,11 @@
737 from unittest import TestCase
738
739-import translations
740+from translations.build import Translations
741
742
743 class HelpTestCase(TestCase):
744 def __init__(self, *args):
745- self.translations = translations.Translations()
746+ self.translations = Translations()
747 TestCase.__init__(self, *args)
748
749 def test_first_line_of_docs_is_title_line(self):
750
751=== added directory 'internals/translations'
752=== added file 'internals/translations/__init__.py'
753=== added file 'internals/translations/build.py'
754--- internals/translations/build.py 1970-01-01 00:00:00 +0000
755+++ internals/translations/build.py 2015-03-19 17:24:01 +0000
756@@ -0,0 +1,304 @@
757+import codecs
758+import glob
759+import os
760+import re
761+import shutil
762+import subprocess
763+
764+from translations.utils import (
765+ find_bcp47_code,
766+ full_path,
767+ normalise_path,
768+ require,
769+ use_top_level_dir,
770+ verify_markdown_file,
771+)
772+
773+from translations.po4a import PO4A
774+
775+try:
776+ import polib
777+except ImportError:
778+ require('python3-polib')
779+
780+from pelicanconf import (
781+ HIDE_FROM_POT,
782+ META_TAGS,
783+ PATH,
784+ TRANSLATIONS_DIR,
785+)
786+
787+
788+class POFile(object):
789+ pofile = None
790+
791+ def __init__(self, po_fn):
792+ self.po_fn = po_fn
793+ self.load()
794+
795+ def load(self):
796+ self.pofile = polib.pofile(full_path(self.po_fn))
797+
798+ def merge(self, pot_file_ob):
799+ self.pofile.merge(pot_file_ob)
800+
801+ def save(self):
802+ self.pofile.save(full_path(self.po_fn))
803+
804+ def find_in_msgid(self, find_str, translated=True, fuzzy=True,
805+ untranslated=True):
806+ entries = []
807+ if translated:
808+ entries += self.pofile.translated_entries()
809+ if fuzzy:
810+ entries += self.pofile.fuzzy_entries()
811+ if untranslated:
812+ entries += self.pofile.untranslated_entries()
813+ results = []
814+ for entry in entries:
815+ if find_str in entry.msgid:
816+ results += [entry]
817+ return results
818+
819+ def hide_attr_list_statements(self):
820+ entries = []
821+ for statement in HIDE_FROM_POT:
822+ entries.extend(self.find_in_msgid(statement))
823+ statements = r'|'.join(HIDE_FROM_POT)
824+ for entry in entries:
825+ matches = re.findall(r'(.*?)\s*?(%s)\s*?' % statements,
826+ entry.msgid)
827+ # [('How do I update my system?', '!!T')]
828+ if len(matches) == 1 and len(matches[0]) == 2:
829+ entry.msgid = matches[0][0]
830+ entry.comment = matches[0][1]
831+ if matches[0][1] in entry.msgstr:
832+ entry.msgstr = entry.msgstr.replace(' %s' % matches[0][1], '')
833+ self.save()
834+
835+ def readd_attr_list_statements(self):
836+ entries = []
837+ for entry_group in [self.pofile.translated_entries(),
838+ self.pofile.fuzzy_entries(),
839+ self.pofile.untranslated_entries()]:
840+ for entry in entry_group:
841+ for statement in HIDE_FROM_POT:
842+ if statement in entry.comment:
843+ entries += [entry]
844+ for entry in entries:
845+ if not entry.msgid.endswith(entry.comment):
846+ entry.msgid += ' %s' % entry.comment
847+ if entry.msgstr and not entry.msgstr.endswith(entry.comment):
848+ entry.msgstr += ' %s' % entry.comment
849+ entry.comment = ''
850+ self.save()
851+
852+ def safeguard_meta_tags(self):
853+ for tag in META_TAGS:
854+ for entry in self.find_in_msgid(tag):
855+ if entry.msgid == tag:
856+ entry.msgstr = entry.msgid
857+ self.save()
858+
859+ def find_title_lines(self):
860+ results = []
861+ for entry in self.find_in_msgid('Title: '):
862+ if entry.msgid.startswith('Title: '):
863+ where = entry.occurrences[0][0]
864+ first_line = codecs.open(full_path(where),
865+ encoding='utf-8').readline().strip()
866+ results += [(entry, first_line)]
867+ return results
868+
869+ def replace_title_lines(self):
870+ for entry, first_line in self.find_title_lines():
871+ if entry.msgid != first_line:
872+ print('Title line "%s" found, but not on the first line '
873+ 'of "%s".' % (entry.msgid, entry.linenum))
874+ return False
875+ entry.msgid = entry.msgid.replace('Title: ', '')
876+ if self.po_fn.endswith('.po'):
877+ entry.msgstr = ''
878+ self.save()
879+ return True
880+
881+ def find_link_in_markdown_message(self, entry):
882+ link_regex = r'\[.+?\]\(\{filename\}(.+?)\).*?'
883+ link_msgid = re.findall(link_regex, entry.msgid)[0]
884+ link_msgstr = list(re.findall(link_regex, entry.msgstr))
885+ return (link_msgid, link_msgstr)
886+
887+ def rewrite_links(self, documents, bcp47):
888+ for entry in self.find_in_msgid('{filename}'):
889+ (link_msgid, link_msgstr) = \
890+ self.find_link_in_markdown_message(entry)
891+ if [doc for doc in documents.docs if doc.endswith(link_msgid)]:
892+ translated_doc_fn = os.path.basename(
893+ documents.translated_doc_fn(link_msgid, bcp47))
894+ if not link_msgstr:
895+ entry.msgstr = entry.msgid
896+ link_msgstr = [link_msgid]
897+ entry.msgstr = entry.msgstr.replace(link_msgstr[0],
898+ translated_doc_fn)
899+ self.save()
900+
901+ def find_translated_title_line(self, original_title):
902+ for entry in self.find_in_msgid(original_title):
903+ if entry.msgid == original_title:
904+ if entry.msgstr:
905+ return entry.msgstr
906+ return entry.msgid
907+
908+
909+class PO(object):
910+ def __init__(self, po4a):
911+ self.fake_lang_code = 'en_US'
912+ self.fake_po_fn = normalise_path(
913+ os.path.join(TRANSLATIONS_DIR,
914+ '%s.po' % self.fake_lang_code))
915+ self.pot_fn = normalise_path(os.path.join(TRANSLATIONS_DIR,
916+ 'help.pot'))
917+ self.pot_file_ob = POFile(self.pot_fn)
918+ self.po4a = po4a
919+ self.langs = {}
920+ for po_fn in glob.glob(TRANSLATIONS_DIR+'/*.po'):
921+ self.add_language(normalise_path(po_fn))
922+
923+ def add_language(self, po_fn):
924+ gettext_code = os.path.basename(po_fn).split('.po')[0]
925+ self.langs[po_fn] = {
926+ 'bcp47': find_bcp47_code(gettext_code),
927+ 'gettext_code': gettext_code,
928+ 'pofile': None,
929+ }
930+
931+ def _remove_fake_po_file(self):
932+ if os.path.exists(self.fake_po_fn):
933+ os.remove(self.fake_po_fn)
934+
935+ def __del__(self):
936+ self._remove_fake_po_file()
937+
938+ def load_pofile(self, po_fn):
939+ if not self.langs[po_fn]['pofile']:
940+ self.langs[po_fn]['pofile'] = POFile(po_fn)
941+
942+ def gettextize(self, documents):
943+ if not self.po4a.gettextize(documents.docs, self.pot_fn):
944+ return False
945+ self.pot_file_ob.load()
946+ return True
947+
948+ def generate_pot_file(self, documents):
949+ if not self.gettextize(documents):
950+ return False
951+ if not self.pot_file_ob.replace_title_lines():
952+ return False
953+ self.pot_file_ob.hide_attr_list_statements()
954+ for po_fn in self.langs:
955+ self.load_pofile(po_fn)
956+ self.langs[po_fn]['pofile'].merge(self.pot_file_ob.pofile)
957+ if not self.langs[po_fn]['pofile'].replace_title_lines():
958+ return False
959+ self.langs[po_fn]['pofile'].hide_attr_list_statements()
960+ return True
961+
962+ # we generate a fake translation for en-US which is going to be
963+ # the default
964+ def generate_fake_pofile(self):
965+ pwd = use_top_level_dir()
966+ self._remove_fake_po_file()
967+ shutil.copy(self.pot_fn, self.fake_po_fn)
968+ os.chdir(pwd)
969+ self.add_language(self.fake_po_fn)
970+
971+ def find_translated_title_line(self, original_title, po_fn):
972+ return self.langs[po_fn]['pofile'].find_translated_title_line(
973+ original_title)
974+
975+ def rewrite_links(self, documents):
976+ for po_fn in self.langs:
977+ self.load_pofile(po_fn)
978+ self.langs[po_fn]['pofile'].rewrite_links(
979+ documents, self.langs[po_fn]['bcp47'])
980+
981+ def safeguard_meta_tags(self):
982+ for po_fn in self.langs:
983+ self.load_pofile(po_fn)
984+ self.langs[po_fn]['pofile'].safeguard_meta_tags()
985+
986+
987+class Documents(object):
988+ def __init__(self):
989+ self.docs = [fn for fn in self.find_docs()
990+ if verify_markdown_file(fn)]
991+
992+ def find_docs(self):
993+ docs = []
994+ for dirpath, dirnames, fns in os.walk(PATH):
995+ docs += [normalise_path(os.path.join(dirpath, fn))
996+ for fn in fns
997+ if fn.endswith('.md')]
998+ return docs
999+
1000+ def translated_doc_fn(self, fn, bcp47_code):
1001+ match = [doc for doc in self.docs
1002+ if os.path.basename(doc) == os.path.basename(fn)]
1003+ if not match:
1004+ return None
1005+ return '%s.%s.md' % (match[0].split('.md')[0],
1006+ bcp47_code)
1007+
1008+ def _call_po4a_translate(self, doc, po_fn, po4a):
1009+ res = po4a.translate(doc, po_fn)
1010+ return codecs.decode(res.communicate()[0])
1011+
1012+ def write_translated_markdown(self, po, po4a):
1013+ for po_fn in po.langs:
1014+ po.langs[po_fn]['pofile'].readd_attr_list_statements()
1015+ for doc_fn in self.docs:
1016+ output = self._call_po4a_translate(doc_fn, po_fn, po4a)
1017+ title_line = output.split('\n')[0].split('Title: ')[1]
1018+ translated_title_line = po.find_translated_title_line(
1019+ title_line, po_fn)
1020+ output = '\n'.join([line for line in output.split('\n')][1:])
1021+ new_path = full_path(self.translated_doc_fn(
1022+ doc_fn, po.langs[po_fn]['bcp47']))
1023+ text = "Title: %s\nDate:\n\n" % (translated_title_line)
1024+ text += output
1025+ if os.path.exists(new_path):
1026+ os.remove(new_path)
1027+ if not os.path.exists(os.path.dirname(new_path)):
1028+ os.makedirs(os.path.dirname(new_path))
1029+ with open(new_path, 'w', encoding='utf-8') as f:
1030+ f.write(text)
1031+ po.langs[po_fn]['pofile'].hide_attr_list_statements()
1032+
1033+
1034+class Translations(object):
1035+ def __init__(self):
1036+ self._cleanup()
1037+ self.documents = Documents()
1038+ self.po4a = PO4A()
1039+ self.po = PO(self.po4a)
1040+
1041+ def _cleanup(self):
1042+ r = subprocess.Popen(['bzr', 'ignored'], stdout=subprocess.PIPE)
1043+ fns = [full_path(f.split(' ')[0])
1044+ for f in codecs.decode(r.communicate()[0]).split('\n')
1045+ if f.strip() != '']
1046+ fns = [f for f in fns if os.path.exists(f)]
1047+ for f in fns:
1048+ try:
1049+ shutil.rmtree(f)
1050+ except NotADirectoryError:
1051+ os.remove(f)
1052+
1053+ def generate_pot_file(self):
1054+ return self.po.generate_pot_file(self.documents)
1055+
1056+ def generate_translations(self):
1057+ self.po.generate_fake_pofile()
1058+ self.po.rewrite_links(self.documents)
1059+ self.po.safeguard_meta_tags()
1060+ self.documents.write_translated_markdown(self.po, self.po4a)
1061
1062=== added file 'internals/translations/po4a.py'
1063--- internals/translations/po4a.py 1970-01-01 00:00:00 +0000
1064+++ internals/translations/po4a.py 2015-03-19 17:24:01 +0000
1065@@ -0,0 +1,62 @@
1066+import copy
1067+import os
1068+import shutil
1069+import subprocess
1070+
1071+from translations.utils import (
1072+ require,
1073+ use_top_level_dir,
1074+)
1075+
1076+# This defines how complete we expect translations to be before we
1077+# generate HTML from them. Needs to be string.
1078+TRANSLATION_COMPLETION_PERCENTAGE = '0'
1079+
1080+
1081+class PO4A(object):
1082+ pwd = None
1083+
1084+ def __init__(self):
1085+ self.default_args = [
1086+ '-f', 'text',
1087+ '-o', 'markdown',
1088+ '-M', 'utf-8',
1089+ ]
1090+ if not shutil.which('po4a'):
1091+ require('po4a')
1092+
1093+ def run(self, po4a_command, additional_args, with_output=False,
1094+ top_level_dir=False):
1095+ if top_level_dir:
1096+ self.pwd = use_top_level_dir()
1097+ args = copy.copy(self.default_args)
1098+ args += additional_args
1099+ if with_output:
1100+ ret = subprocess.Popen([po4a_command]+args, stdout=subprocess.PIPE)
1101+ else:
1102+ ret = subprocess.call([po4a_command]+args)
1103+ if top_level_dir:
1104+ os.chdir(self.pwd)
1105+ self.pwd = None
1106+ return ret
1107+
1108+ def gettextize(self, document_fns, pot_file):
1109+ args = copy.copy(self.default_args)
1110+ for document_fn in document_fns:
1111+ args += ['-m', document_fn]
1112+ args += [
1113+ '-p', pot_file,
1114+ '-L', 'utf-8',
1115+ ]
1116+ ret = self.run('po4a-gettextize', args, top_level_dir=True)
1117+ return (not ret)
1118+
1119+ def translate(self, doc, po_fn):
1120+ args = [
1121+ '-k', TRANSLATION_COMPLETION_PERCENTAGE,
1122+ '-m', doc,
1123+ '-p', po_fn,
1124+ '-L', 'utf-8',
1125+ ]
1126+ return self.run('po4a-translate', args, with_output=True,
1127+ top_level_dir=True)
1128
1129=== renamed file 'edit-here/translations.py' => 'internals/translations/utils.py'
1130--- edit-here/translations.py 2015-03-17 09:24:48 +0000
1131+++ internals/translations/utils.py 2015-03-19 17:24:01 +0000
1132@@ -1,10 +1,5 @@
1133 import codecs
1134-import copy
1135-import glob
1136 import os
1137-import re
1138-import shutil
1139-import subprocess
1140 import sys
1141 import tempfile
1142
1143@@ -15,11 +10,6 @@
1144 sys.exit(1)
1145
1146 try:
1147- import polib
1148-except ImportError:
1149- require('python3-polib')
1150-
1151-try:
1152 import magic
1153 except:
1154 require('python3-magic')
1155@@ -29,11 +19,10 @@
1156 except:
1157 require('python3-markdown')
1158
1159-from pelicanconf import PATH, MD_EXTENSIONS, META_TAGS, HIDE_FROM_POT
1160-
1161-# This defines how complete we expect translations to be before we
1162-# generate HTML from them. Needs to be string.
1163-TRANSLATION_COMPLETION_PERCENTAGE = '0'
1164+from pelicanconf import (
1165+ MD_EXTENSIONS,
1166+ TOP_LEVEL_DIR,
1167+)
1168
1169 BCP47_OVERRIDES = (
1170 ('zh_CN', 'zh-hans'),
1171@@ -47,6 +36,20 @@
1172 ]
1173
1174
1175+def normalise_path(path):
1176+ return os.path.relpath(path, TOP_LEVEL_DIR)
1177+
1178+
1179+def full_path(path):
1180+ return os.path.abspath(os.path.join(TOP_LEVEL_DIR, path))
1181+
1182+
1183+def use_top_level_dir():
1184+ pwd = os.getcwd()
1185+ os.chdir(TOP_LEVEL_DIR)
1186+ return pwd
1187+
1188+
1189 def _temp_write_markdown(fn):
1190 (ret, tmp) = tempfile.mkstemp()
1191 length = 0
1192@@ -65,6 +68,7 @@
1193 def verify_markdown_file(fn):
1194 ms = magic.open(magic.MAGIC_NONE)
1195 ms.load()
1196+ fn = full_path(fn)
1197 if ms.file(fn) not in MD_MAGIC_FILE_TYPES:
1198 return False
1199 (ret, length) = _temp_write_markdown(fn)
1200@@ -78,314 +82,3 @@
1201 return gettext_code.lower().replace('_', '-')
1202 return [c[1] for c in BCP47_OVERRIDES
1203 if c[0] == gettext_code][0]
1204-
1205-
1206-class PO4A(object):
1207- def __init__(self):
1208- self.default_args = [
1209- '-f', 'text',
1210- '-o', 'markdown',
1211- '-M', 'utf-8',
1212- ]
1213- if not shutil.which('po4a'):
1214- require('po4a')
1215-
1216- def run(self, po4a_command, additional_args, with_output=False):
1217- args = copy.copy(self.default_args)
1218- args += additional_args
1219- if with_output:
1220- ret = subprocess.Popen([po4a_command]+args, stdout=subprocess.PIPE)
1221- else:
1222- ret = subprocess.call([po4a_command]+args)
1223- return ret
1224-
1225- def gettextize(self, document_fns, pot_file):
1226- args = copy.copy(self.default_args)
1227- for document_fn in document_fns:
1228- args += ['-m', document_fn]
1229- args += [
1230- '-p', pot_file,
1231- '-L', 'utf-8',
1232- ]
1233- ret = self.run('po4a-gettextize', args)
1234- return (not ret)
1235-
1236- def translate(self, doc, po_fn):
1237- args = [
1238- '-k', TRANSLATION_COMPLETION_PERCENTAGE,
1239- '-m', doc,
1240- '-p', po_fn,
1241- '-L', 'utf-8',
1242- ]
1243- return self.run('po4a-translate', args, with_output=True)
1244-
1245-
1246-class POFile(object):
1247- def __init__(self, po_fn):
1248- self.po_fn = po_fn
1249- self.pofile = polib.pofile(po_fn)
1250-
1251- def reread(self):
1252- self.pofile = polib.pofile(self.po_fn)
1253-
1254- def merge(self, pot_file_ob):
1255- self.pofile.merge(pot_file_ob)
1256-
1257- def save(self):
1258- self.pofile.save(self.po_fn)
1259-
1260- def find_in_msgid(self, find_str, translated=True, fuzzy=True,
1261- untranslated=True):
1262- entries = []
1263- if translated:
1264- entries += self.pofile.translated_entries()
1265- if fuzzy:
1266- entries += self.pofile.fuzzy_entries()
1267- if untranslated:
1268- entries += self.pofile.untranslated_entries()
1269- results = []
1270- for entry in entries:
1271- if find_str in entry.msgid:
1272- results += [entry]
1273- return results
1274-
1275- def hide_attr_list_statements(self):
1276- entries = []
1277- for statement in HIDE_FROM_POT:
1278- entries.extend(self.find_in_msgid(statement))
1279- statements = r'|'.join(HIDE_FROM_POT)
1280- for entry in entries:
1281- matches = re.findall(r'(.*?)\s*?(%s)\s*?' % statements,
1282- entry.msgid)
1283- # [('How do I update my system?', '!!T')]
1284- if len(matches) == 1 and len(matches[0]) == 2:
1285- entry.msgid = matches[0][0]
1286- entry.comment = matches[0][1]
1287- if matches[0][1] in entry.msgstr:
1288- entry.msgstr = entry.msgstr.replace(' %s' % matches[0][1], '')
1289- self.save()
1290-
1291- def readd_attr_list_statements(self):
1292- entries = []
1293- for entry_group in [self.pofile.translated_entries(),
1294- self.pofile.fuzzy_entries(),
1295- self.pofile.untranslated_entries()]:
1296- for entry in entry_group:
1297- for statement in HIDE_FROM_POT:
1298- if statement in entry.comment:
1299- entries += [entry]
1300- for entry in entries:
1301- if not entry.msgid.endswith(entry.comment):
1302- entry.msgid += ' %s' % entry.comment
1303- if entry.msgstr and not entry.msgstr.endswith(entry.comment):
1304- entry.msgstr += ' %s' % entry.comment
1305- entry.comment = ''
1306- self.save()
1307-
1308- def safeguard_meta_tags(self):
1309- for tag in META_TAGS:
1310- for entry in self.find_in_msgid(tag):
1311- if entry.msgid == tag:
1312- entry.msgstr = entry.msgid
1313- self.save()
1314-
1315- def find_title_lines(self):
1316- results = []
1317- for entry in self.find_in_msgid('Title: '):
1318- if entry.msgid.startswith('Title: '):
1319- where = entry.occurrences[0][0]
1320- first_line = codecs.open(where,
1321- encoding='utf-8').readline().strip()
1322- results += [(entry, first_line)]
1323- return results
1324-
1325- def replace_title_lines(self):
1326- for entry, first_line in self.find_title_lines():
1327- if entry.msgid != first_line:
1328- print('Title line "%s" found, but not on the first line '
1329- 'of "%s".' % (entry.msgid, entry.linenum))
1330- return False
1331- entry.msgid = entry.msgid.replace('Title: ', '')
1332- if self.po_fn.endswith('.po'):
1333- entry.msgstr = ''
1334- self.save()
1335- return True
1336-
1337- def find_link_in_markdown_message(self, entry):
1338- link_regex = r'\[.+?\]\(\{filename\}(.+?)\).*?'
1339- link_msgid = re.findall(link_regex, entry.msgid)[0]
1340- link_msgstr = list(re.findall(link_regex, entry.msgstr))
1341- return (link_msgid, link_msgstr)
1342-
1343- def rewrite_links(self, documents, bcp47):
1344- for entry in self.find_in_msgid('{filename}'):
1345- (link_msgid, link_msgstr) = \
1346- self.find_link_in_markdown_message(entry)
1347- if [doc for doc in documents.docs if doc.endswith(link_msgid)]:
1348- translated_doc_fn = os.path.basename(
1349- documents.translated_doc_fn(link_msgid, bcp47))
1350- if not link_msgstr:
1351- entry.msgstr = entry.msgid
1352- link_msgstr = [link_msgid]
1353- entry.msgstr = entry.msgstr.replace(link_msgstr[0],
1354- translated_doc_fn)
1355- self.save()
1356-
1357- def find_translated_title_line(self, original_title):
1358- for entry in self.find_in_msgid(original_title):
1359- if entry.msgid == original_title:
1360- if entry.msgstr:
1361- return entry.msgstr
1362- return entry.msgid
1363-
1364-
1365-class PO(object):
1366- def __init__(self, po4a):
1367- self.translations_dir = os.path.abspath(os.path.join(PATH, '../po'))
1368- self.fake_lang_code = 'en_US'
1369- self.fake_po_fn = os.path.join(self.translations_dir,
1370- '%s.po' % self.fake_lang_code)
1371- self.pot_fn = os.path.join(self.translations_dir, 'help.pot')
1372- self.pot_file_ob = POFile(self.pot_fn)
1373- self.po4a = po4a
1374- self.langs = {}
1375- for po_fn in glob.glob(self.translations_dir+'/*.po'):
1376- self.add_language(po_fn)
1377-
1378- def add_language(self, po_fn):
1379- gettext_code = os.path.basename(po_fn).split('.po')[0]
1380- self.langs[po_fn] = {
1381- 'bcp47': find_bcp47_code(gettext_code),
1382- 'gettext_code': gettext_code,
1383- 'pofile': None,
1384- }
1385-
1386- def _remove_fake_po_file(self):
1387- if os.path.exists(self.fake_po_fn):
1388- os.remove(self.fake_po_fn)
1389-
1390- def __del__(self):
1391- self._remove_fake_po_file()
1392-
1393- def load_pofile(self, po_fn):
1394- if not self.langs[po_fn]['pofile']:
1395- self.langs[po_fn]['pofile'] = POFile(po_fn)
1396-
1397- def gettextize(self, documents):
1398- if not self.po4a.gettextize(documents.docs, self.pot_fn):
1399- return False
1400- self.pot_file_ob.reread()
1401- return True
1402-
1403- def generate_pot_file(self, documents):
1404- if not self.gettextize(documents):
1405- return False
1406- if not self.pot_file_ob.replace_title_lines():
1407- return False
1408- self.pot_file_ob.hide_attr_list_statements()
1409- for po_fn in self.langs:
1410- self.load_pofile(po_fn)
1411- self.langs[po_fn]['pofile'].merge(self.pot_file_ob.pofile)
1412- if not self.langs[po_fn]['pofile'].replace_title_lines():
1413- return False
1414- self.langs[po_fn]['pofile'].hide_attr_list_statements()
1415- return True
1416-
1417- # we generate a fake translation for en-US which is going to be
1418- # the default
1419- def generate_fake_pofile(self):
1420- self._remove_fake_po_file()
1421- shutil.copy(self.pot_fn, self.fake_po_fn)
1422- self.add_language(self.fake_po_fn)
1423-
1424- def find_translated_title_line(self, original_title, po_fn):
1425- return self.langs[po_fn]['pofile'].find_translated_title_line(
1426- original_title)
1427-
1428- def rewrite_links(self, documents):
1429- for po_fn in self.langs:
1430- self.load_pofile(po_fn)
1431- self.langs[po_fn]['pofile'].rewrite_links(
1432- documents, self.langs[po_fn]['bcp47'])
1433-
1434- def safeguard_meta_tags(self):
1435- for po_fn in self.langs:
1436- self.load_pofile(po_fn)
1437- self.langs[po_fn]['pofile'].safeguard_meta_tags()
1438-
1439-
1440-class Documents(object):
1441- def __init__(self):
1442- self.docs = [fn for fn in self.find_docs()
1443- if verify_markdown_file(fn)]
1444-
1445- def find_docs(self):
1446- docs = []
1447- for dirpath, dirnames, fns in os.walk(PATH):
1448- docs += [os.path.relpath(os.path.join(dirpath, fn),
1449- os.path.join(PATH, '..'))
1450- for fn in fns
1451- if fn.endswith('.md')]
1452- return docs
1453-
1454- def translated_doc_fn(self, fn, bcp47_code):
1455- match = [doc for doc in self.docs
1456- if os.path.basename(doc) == os.path.basename(fn)]
1457- if not match:
1458- return None
1459- return '%s.%s.md' % (match[0].split('.md')[0],
1460- bcp47_code)
1461-
1462- def _call_po4a_translate(self, doc, po_fn, po4a):
1463- res = po4a.translate(doc, po_fn)
1464- return codecs.decode(res.communicate()[0])
1465-
1466- def write_translated_markdown(self, po, po4a):
1467- for po_fn in po.langs:
1468- po.langs[po_fn]['pofile'].readd_attr_list_statements()
1469- for doc_fn in self.docs:
1470- output = self._call_po4a_translate(doc_fn, po_fn, po4a)
1471- title_line = output.split('\n')[0].split('Title: ')[1]
1472- translated_title_line = po.find_translated_title_line(
1473- title_line, po_fn)
1474- output = '\n'.join([line for line in output.split('\n')][1:])
1475- new_path = self.translated_doc_fn(doc_fn,
1476- po.langs[po_fn]['bcp47'])
1477- text = "Title: %s\nDate:\n\n" % (translated_title_line)
1478- text += output
1479- if os.path.exists(new_path):
1480- os.remove(new_path)
1481- if not os.path.exists(os.path.dirname(new_path)):
1482- os.makedirs(os.path.dirname(new_path))
1483- with open(new_path, 'w', encoding='utf-8') as f:
1484- f.write(text)
1485- po.langs[po_fn]['pofile'].hide_attr_list_statements()
1486-
1487-
1488-class Translations(object):
1489- def __init__(self):
1490- self._cleanup()
1491- self.documents = Documents()
1492- self.po4a = PO4A()
1493- self.po = PO(self.po4a)
1494-
1495- def _cleanup(self):
1496- r = subprocess.Popen(['bzr', 'ignored'], stdout=subprocess.PIPE)
1497- fns = [os.path.join(PATH, '../..', f.split(' ')[0])
1498- for f in codecs.decode(r.communicate()[0]).split('\n')
1499- if f.strip() != '']
1500- fns = [f for f in fns if os.path.exists(f)]
1501- for f in fns:
1502- try:
1503- shutil.rmtree(f)
1504- except NotADirectoryError:
1505- os.remove(f)
1506-
1507- def generate_pot_file(self):
1508- return self.po.generate_pot_file(self.documents)
1509-
1510- def generate_translations(self):
1511- self.po.generate_fake_pofile()
1512- self.po.rewrite_links(self.documents)
1513- self.po.safeguard_meta_tags()
1514- self.documents.write_translated_markdown(self.po, self.po4a)
1515
1516=== renamed directory 'edit-here/po' => 'po'
1517=== modified file 'po/help.pot'
1518--- edit-here/po/help.pot 2015-03-19 11:16:04 +0000
1519+++ po/help.pot 2015-03-19 17:24:01 +0000
1520@@ -2,12 +2,12 @@
1521 # Copyright (C) YEAR Free Software Foundation, Inc.
1522 # This file is distributed under the same license as the PACKAGE package.
1523 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
1524-#
1525+#
1526 #, fuzzy
1527 msgid ""
1528 msgstr ""
1529 "Project-Id-Version: PACKAGE VERSION\n"
1530-"POT-Creation-Date: 2015-03-19 12:15+0100\n"
1531+"POT-Creation-Date: 2015-03-19 15:41+0100\n"
1532 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1533 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1534 "Language-Team: LANGUAGE <LL@li.org>\n"
1535@@ -18,7 +18,7 @@
1536
1537 #. type: Plain text
1538 #: content/pages/security.md:2
1539-msgid "Security"
1540+msgid "Title: Security"
1541 msgstr ""
1542
1543 #. type: Plain text
1544@@ -28,16 +28,14 @@
1545 msgstr ""
1546
1547 #. type: Plain text
1548-#: content/pages/security.md:6 content/pages/basic.md:6
1549-#: content/pages/settings.md:6 content/pages/ui.md:6 content/pages/apps.md:7
1550-#: content/pages/scopes.md:6
1551+#: content/pages/security.md:6 content/pages/basic.md:6 content/pages/settings.md:6 content/pages/ui.md:6 content/pages/apps.md:7 content/pages/scopes.md:6
1552 msgid "[TOC]"
1553 msgstr ""
1554
1555-#. !!T
1556+#. type: Title ###
1557 #: content/pages/security.md:7
1558 #, no-wrap
1559-msgid "How do I lock the phone?"
1560+msgid "How do I lock the phone? !!T"
1561 msgstr ""
1562
1563 #. type: Plain text
1564@@ -49,10 +47,10 @@
1565 "& Privacy*, then *Phone Locking* to adjust the *Lock when idle* setting."
1566 msgstr ""
1567
1568-#. !!T
1569+#. type: Title ###
1570 #: content/pages/security.md:10
1571 #, no-wrap
1572-msgid "How do I unlock the phone?"
1573+msgid "How do I unlock the phone? !!T"
1574 msgstr ""
1575
1576 #. type: Plain text
1577@@ -62,10 +60,10 @@
1578 "enabled, you might be required to enter a pin or passcode."
1579 msgstr ""
1580
1581-#. !!T
1582+#. type: Title ###
1583 #: content/pages/security.md:13
1584 #, no-wrap
1585-msgid "How do I unlock the bootloader?"
1586+msgid "How do I unlock the bootloader? !!T"
1587 msgstr ""
1588
1589 #. type: Plain text
1590@@ -75,10 +73,10 @@
1591 "related tasks, see the [developer site](http://developer.ubuntu.com/)"
1592 msgstr ""
1593
1594-#. !!T
1595+#. type: Title ###
1596 #: content/pages/security.md:16
1597 #, no-wrap
1598-msgid "How can I change my PIN/Passcode?"
1599+msgid "How can I change my PIN/Passcode? !!T"
1600 msgstr ""
1601
1602 #. type: Plain text
1603@@ -88,12 +86,12 @@
1604 "*Security & Privacy* to adjust the *Lock when idle* setting."
1605 msgstr ""
1606
1607-#. !!T
1608+#. type: Title ###
1609 #: content/pages/security.md:19
1610 #, no-wrap
1611 msgid ""
1612-"Why do I have to type my PIN when using File Manager & Terminal (not default"
1613-" apps)?"
1614+"Why do I have to type my PIN when using File Manager & Terminal (not default "
1615+"apps)? !!T"
1616 msgstr ""
1617
1618 #. type: Plain text
1619@@ -103,11 +101,10 @@
1620 "pin/passcode is required. This is for your phone security."
1621 msgstr ""
1622
1623-#. !!T
1624+#. type: Title ###
1625 #: content/pages/security.md:22
1626 #, no-wrap
1627-msgid ""
1628-"How can I stop someone using the indicators when the phone is unlocked?"
1629+msgid "How can I stop someone using the indicators when the phone is unlocked? !!T"
1630 msgstr ""
1631
1632 #. type: Plain text
1633@@ -119,10 +116,10 @@
1634 "settings* option."
1635 msgstr ""
1636
1637-#. !!T
1638+#. type: Title ###
1639 #: content/pages/security.md:25
1640 #, no-wrap
1641-msgid "I forgot my password or passcode. How can I unlock the phone?"
1642+msgid "I forgot my password or passcode. How can I unlock the phone? !!T"
1643 msgstr ""
1644
1645 #. type: Plain text
1646@@ -132,7 +129,7 @@
1647
1648 #. type: Plain text
1649 #: content/pages/basic.md:2
1650-msgid "Basic tasks"
1651+msgid "Title: Basic tasks"
1652 msgstr ""
1653
1654 #. type: Plain text
1655@@ -141,37 +138,37 @@
1656 msgid "*If you are wondering how to perform basic tasks, look here.*\n"
1657 msgstr ""
1658
1659-#. !!T
1660+#. type: Title ###
1661 #: content/pages/basic.md:7
1662 #, no-wrap
1663-msgid "How do I play music?"
1664+msgid "How do I play music? !!T"
1665 msgstr ""
1666
1667 #. type: Plain text
1668 #: content/pages/basic.md:9
1669 msgid ""
1670 "The music app let's you play music copied to the device. In addition, "
1671-"[scopes]({filename}scopes.md) such as 7digital and Grooveshark can also play"
1672-" music."
1673+"[scopes]({filename}scopes.md) such as 7digital and Grooveshark can also play "
1674+"music."
1675 msgstr ""
1676
1677-#. !!T
1678+#. type: Title ###
1679 #: content/pages/basic.md:10
1680 #, no-wrap
1681-msgid "How do I play videos?"
1682+msgid "How do I play videos? !!T"
1683 msgstr ""
1684
1685 #. type: Plain text
1686 #: content/pages/basic.md:12
1687 msgid ""
1688-"The media player application will play videos copied to the device. You will"
1689-" also find applications like youtube that give you streaming options."
1690+"The media player application will play videos copied to the device. You will "
1691+"also find applications like youtube that give you streaming options."
1692 msgstr ""
1693
1694-#. !!T
1695+#. type: Title ###
1696 #: content/pages/basic.md:13
1697 #, no-wrap
1698-msgid "How do I take photos?"
1699+msgid "How do I take photos? !!T"
1700 msgstr ""
1701
1702 #. type: Plain text
1703@@ -181,38 +178,38 @@
1704 "has both a front and rear camera, you can toggle which camera to use."
1705 msgstr ""
1706
1707-#. !!T
1708+#. type: Title ###
1709 #: content/pages/basic.md:16
1710 #, no-wrap
1711-msgid "How do I take a screenshot?"
1712+msgid "How do I take a screenshot? !!T"
1713 msgstr ""
1714
1715 #. type: Plain text
1716 #: content/pages/basic.md:18
1717 msgid ""
1718 "Press the Volume Up and Volume Down buttons at the same time until you see "
1719-"the screen flashing. The screenshot will capture what is on your screen, and"
1720-" you can see the resulting picture in the Gallery app or the Photos scope."
1721+"the screen flashing. The screenshot will capture what is on your screen, and "
1722+"you can see the resulting picture in the Gallery app or the Photos scope."
1723 msgstr ""
1724
1725-#. !!T
1726+#. type: Title ###
1727 #: content/pages/basic.md:19
1728 #, no-wrap
1729-msgid "How do I see pictures I’ve taken?"
1730+msgid "How do I see pictures I’ve taken? !!T"
1731 msgstr ""
1732
1733 #. type: Plain text
1734 #: content/pages/basic.md:21
1735 msgid ""
1736-"If you've just taken a picture, you can see it easily by swiping to the left"
1737-" from the right edge inside the camera app. Alternatively, use the gallery "
1738+"If you've just taken a picture, you can see it easily by swiping to the left "
1739+"from the right edge inside the camera app. Alternatively, use the gallery "
1740 "app to find the picture."
1741 msgstr ""
1742
1743-#. !!T
1744+#. type: Title ###
1745 #: content/pages/basic.md:22
1746 #, no-wrap
1747-msgid "How do I record videos?"
1748+msgid "How do I record videos? !!T"
1749 msgstr ""
1750
1751 #. type: Plain text
1752@@ -222,10 +219,10 @@
1753 "has both a front and rear camera, you can toggle which camera to use."
1754 msgstr ""
1755
1756-#. !!T
1757+#. type: Title ###
1758 #: content/pages/basic.md:25
1759 #, no-wrap
1760-msgid "How can I send a text?"
1761+msgid "How can I send a text? !!T"
1762 msgstr ""
1763
1764 #. type: Plain text
1765@@ -235,10 +232,10 @@
1766 "messages."
1767 msgstr ""
1768
1769-#. !!T
1770+#. type: Title ###
1771 #: content/pages/basic.md:28
1772 #, no-wrap
1773-msgid "How do I make a call?"
1774+msgid "How do I make a call? !!T"
1775 msgstr ""
1776
1777 #. type: Plain text
1778@@ -248,10 +245,10 @@
1779 "number."
1780 msgstr ""
1781
1782-#. !!T
1783+#. type: Title ###
1784 #: content/pages/basic.md:31
1785 #, no-wrap
1786-msgid "How do I check recently made/missed calls?"
1787+msgid "How do I check recently made/missed calls? !!T"
1788 msgstr ""
1789
1790 #. type: Plain text
1791@@ -263,7 +260,7 @@
1792
1793 #. type: Plain text
1794 #: content/pages/faq.md:2
1795-msgid "Get your questions answered."
1796+msgid "Title: Get your questions answered."
1797 msgstr ""
1798
1799 #. type: Plain text
1800@@ -311,7 +308,7 @@
1801
1802 #. type: Plain text
1803 #: content/pages/index.md:2
1804-msgid "Welcome"
1805+msgid "Title: Welcome"
1806 msgstr ""
1807
1808 #. type: Plain text
1809@@ -348,7 +345,7 @@
1810
1811 #. type: Plain text
1812 #: content/pages/settings.md:2
1813-msgid "Settings"
1814+msgid "Title: Settings"
1815 msgstr ""
1816
1817 #. type: Plain text
1818@@ -357,10 +354,10 @@
1819 msgid "*How do I change my phone settings?*\n"
1820 msgstr ""
1821
1822-#. !!T
1823+#. type: Title ###
1824 #: content/pages/settings.md:7
1825 #, no-wrap
1826-msgid "How do I update my system?"
1827+msgid "How do I update my system? !!T"
1828 msgstr ""
1829
1830 #. type: Plain text
1831@@ -368,14 +365,14 @@
1832 msgid ""
1833 "Your device will prompt you when an update is ready. A notification will "
1834 "appear informing you of the new update. If you wish, you can manually check "
1835-"and perform an update yourself. Open the *System Settings* application. "
1836-"Select *Update*, and then click the check for updates button."
1837+"and perform an update yourself. Open the *System Settings* "
1838+"application. Select *Update*, and then click the check for updates button."
1839 msgstr ""
1840
1841-#. !!T
1842+#. type: Title ###
1843 #: content/pages/settings.md:10
1844 #, no-wrap
1845-msgid "How do I set the time / language?"
1846+msgid "How do I set the time / language? !!T"
1847 msgstr ""
1848
1849 #. type: Plain text
1850@@ -386,23 +383,24 @@
1851 "![Icon]({filename}/images/settings.gif)"
1852 msgstr ""
1853
1854-#. !!T
1855+#. type: Title ###
1856 #: content/pages/settings.md:14
1857 #, no-wrap
1858-msgid "How can I change my wallpaper/background?"
1859+msgid "How can I change my wallpaper/background? !!T"
1860 msgstr ""
1861
1862 #. type: Plain text
1863 #: content/pages/settings.md:16
1864 msgid ""
1865-"Open the *System Settings* application. Select the *Background* option. "
1866-"Press the *Add Image* button and choice your image to set as a background."
1867+"Open the *System Settings* application. Select the *Background* "
1868+"option. Press the *Add Image* button and choice your image to set as a "
1869+"background."
1870 msgstr ""
1871
1872-#. !!T
1873+#. type: Title ###
1874 #: content/pages/settings.md:17
1875 #, no-wrap
1876-msgid "How do I keep the screen on?"
1877+msgid "How do I keep the screen on? !!T"
1878 msgstr ""
1879
1880 #. type: Plain text
1881@@ -412,26 +410,26 @@
1882 "option. Select the *Lock Phone* option, and then *Lock when idle*. St"
1883 msgstr ""
1884
1885-#. !!T
1886+#. type: Title ###
1887 #: content/pages/settings.md:20
1888 #, no-wrap
1889-msgid "How do I set up my accounts?"
1890+msgid "How do I set up my accounts? !!T"
1891 msgstr ""
1892
1893 #. type: Plain text
1894 #: content/pages/settings.md:22
1895 msgid ""
1896-"You can set up some of your accounts from the scopes. Today scope allows you"
1897-" to configure your Google and Fitbit account, while the Pictures scope lets "
1898+"You can set up some of your accounts from the scopes. Today scope allows you "
1899+"to configure your Google and Fitbit account, while the Pictures scope lets "
1900 "you configure your flickr, Facebook and Instagram account. You can manage "
1901 "all your accounts (including social media, email, etc) from the *System "
1902 "Settings* app, under *Personal*, *Accounts*."
1903 msgstr ""
1904
1905-#. !!T
1906+#. type: Title ###
1907 #: content/pages/settings.md:23
1908 #, no-wrap
1909-msgid "How do I configure my notifications?"
1910+msgid "How do I configure my notifications? !!T"
1911 msgstr ""
1912
1913 #. type: Plain text
1914@@ -443,10 +441,10 @@
1915 "notifications from any application on your device."
1916 msgstr ""
1917
1918-#. !!T
1919+#. type: Title ###
1920 #: content/pages/settings.md:26
1921 #, no-wrap
1922-msgid "How do I change the ringtone for calls and texts?"
1923+msgid "How do I change the ringtone for calls and texts? !!T"
1924 msgstr ""
1925
1926 #. type: Plain text
1927@@ -460,36 +458,36 @@
1928
1929 #. type: Plain text
1930 #: content/pages/ui.md:2
1931-msgid "User Interface"
1932+msgid "Title: User Interface"
1933 msgstr ""
1934
1935 #. type: Plain text
1936 #: content/pages/ui.md:4
1937 #, no-wrap
1938 msgid ""
1939-"*Are you wondering about the dash, scopes, swiping? You've come to the right"
1940-" place!*\n"
1941+"*Are you wondering about the dash, scopes, swiping? You've come to the right "
1942+"place!*\n"
1943 msgstr ""
1944
1945-#. !!T
1946+#. type: Title ###
1947 #: content/pages/ui.md:7
1948 #, no-wrap
1949-msgid "What is the dash?"
1950+msgid "What is the dash? !!T"
1951 msgstr ""
1952
1953 #. type: Plain text
1954 #: content/pages/ui.md:9
1955 msgid ""
1956-"The dash contains a list of applications installed on the device, along with"
1957-" presenting the scopes and store. The dash is the first thing you see when "
1958+"The dash contains a list of applications installed on the device, along with "
1959+"presenting the scopes and store. The dash is the first thing you see when "
1960 "booting the phone. You can switch to it again at any time by swiping left "
1961 "from the right screen edge."
1962 msgstr ""
1963
1964-#. !!T
1965+#. type: Title ###
1966 #: content/pages/ui.md:10
1967 #, no-wrap
1968-msgid "What is the launcher?"
1969+msgid "What is the launcher? !!T"
1970 msgstr ""
1971
1972 #. type: Plain text
1973@@ -499,10 +497,10 @@
1974 "the launcher at any time by swiping right from the left screen edge."
1975 msgstr ""
1976
1977-#. !!T
1978+#. type: Title ###
1979 #: content/pages/ui.md:13
1980 #, no-wrap
1981-msgid "How can I customize the launcher?"
1982+msgid "How can I customize the launcher? !!T"
1983 msgstr ""
1984
1985 #. type: Plain text
1986@@ -515,69 +513,68 @@
1987 "in."
1988 msgstr ""
1989
1990-#. !!T
1991+#. type: Title ###
1992 #: content/pages/ui.md:17
1993 #, no-wrap
1994-msgid "What are the indicators?"
1995+msgid "What are the indicators? !!T"
1996 msgstr ""
1997
1998 #. type: Plain text
1999 #: content/pages/ui.md:19
2000 msgid ""
2001-"Indicators convey quick useful information about your device, like the time,"
2002-" data connection, location, sound, and notifications. You can access the "
2003+"Indicators convey quick useful information about your device, like the time, "
2004+"data connection, location, sound, and notifications. You can access the "
2005 "indicators at any time by swiping down from the top screen edge."
2006 msgstr ""
2007
2008-#. !!T
2009+#. type: Title ###
2010 #: content/pages/ui.md:20
2011 #, no-wrap
2012-msgid "How do I switch applications?"
2013+msgid "How do I switch applications? !!T"
2014 msgstr ""
2015
2016 #. type: Plain text
2017 #: content/pages/ui.md:23
2018 msgid ""
2019 "To switch applications, slide your finger left from the right edge of the "
2020-"screen. If you slide quickly you will cycle through each application. "
2021-"However, if you slide more slowly, an application switcher will appear "
2022-"allowing you to select the application you wish to switch to, including the "
2023-"dash."
2024+"screen. If you slide quickly you will cycle through each "
2025+"application. However, if you slide more slowly, an application switcher will "
2026+"appear allowing you to select the application you wish to switch to, "
2027+"including the dash."
2028 msgstr ""
2029
2030-#. !!T
2031+#. type: Title ###
2032 #: content/pages/ui.md:24
2033 #, no-wrap
2034-msgid "How do I close applications?"
2035+msgid "How do I close applications? !!T"
2036 msgstr ""
2037
2038 #. type: Plain text
2039 #: content/pages/ui.md:26
2040 msgid ""
2041-"To close an application, slide your finger *slowly* left from the right edge"
2042-" of the screen. An application switcher will appear. Place your finger on "
2043-"the application preview you wish to close and swipe up or down. The "
2044-"application will disappear."
2045+"To close an application, slide your finger *slowly* left from the right edge "
2046+"of the screen. An application switcher will appear. Place your finger on the "
2047+"application preview you wish to close and swipe up or down. The application "
2048+"will disappear."
2049 msgstr ""
2050
2051-#. !!T
2052+#. type: Title ###
2053 #: content/pages/ui.md:27
2054 #, no-wrap
2055-msgid "How can I copy and paste?"
2056+msgid "How can I copy and paste? !!T"
2057 msgstr ""
2058
2059 #. type: Plain text
2060 #: content/pages/ui.md:29
2061 msgid ""
2062-"For text that can be copied and pasted, press and hold the text in question."
2063-" A menu will appear allowing you to cut, copy and paste."
2064+"For text that can be copied and pasted, press and hold the text in "
2065+"question. A menu will appear allowing you to cut, copy and paste."
2066 msgstr ""
2067
2068-#. !!T
2069+#. type: Title ###
2070 #: content/pages/ui.md:30
2071 #, no-wrap
2072-msgid ""
2073-"What are the small characters on the keyboard and how can I select them?"
2074+msgid "What are the small characters on the keyboard and how can I select them? !!T"
2075 msgstr ""
2076
2077 #. type: Plain text
2078@@ -588,10 +585,10 @@
2079 "numbers and accented characters. Give it a try!"
2080 msgstr ""
2081
2082-#. !!T
2083+#. type: Title ###
2084 #: content/pages/ui.md:33
2085 #, no-wrap
2086-msgid "The keyboard behaves funny. What can I do about it?"
2087+msgid "The keyboard behaves funny. What can I do about it? !!T"
2088 msgstr ""
2089
2090 #. type: Plain text
2091@@ -603,10 +600,10 @@
2092 "control of the input."
2093 msgstr ""
2094
2095-#. !!T
2096+#. type: Title ###
2097 #: content/pages/ui.md:36
2098 #, no-wrap
2099-msgid "How can I add a new keyboard language?"
2100+msgid "How can I add a new keyboard language? !!T"
2101 msgstr ""
2102
2103 #. type: Plain text
2104@@ -616,14 +613,14 @@
2105 "that might not be available in other keyboards and (if available) use word "
2106 "prediction and spell checking for that new language. Go to the Settings "
2107 "app, tap on Language/Text and then on Keyboard layouts. From there, you can "
2108-"then tick the keyboard you want to add and then [use it](#how-can-i-switch-"
2109-"between-keyboard-languages)"
2110+"then tick the keyboard you want to add and then [use "
2111+"it](#how-can-i-switch-between-keyboard-languages)"
2112 msgstr ""
2113
2114-#. !!T
2115+#. type: Title ###
2116 #: content/pages/ui.md:40
2117 #, no-wrap
2118-msgid "How can I switch between keyboard languages?"
2119+msgid "How can I switch between keyboard languages? !!T"
2120 msgstr ""
2121
2122 #. type: Plain text
2123@@ -636,42 +633,43 @@
2124 "activated it in the Settings app](#how-can-i-add-a-new-keyboard-language)."
2125 msgstr ""
2126
2127-#. !!T
2128+#. type: Title ###
2129 #: content/pages/ui.md:44
2130 #, no-wrap
2131-msgid "How can I type Emoji icons?"
2132+msgid "How can I type Emoji icons? !!T"
2133 msgstr ""
2134
2135 #. type: Plain text
2136 #: content/pages/ui.md:47
2137 msgid ""
2138-"[Learn wow to switch between keyboard languages](#how-can-i-switch-between-"
2139-"keyboard-languages) to alternate between the regular keyboard and the Emoji "
2140-"keyboard. Once done with typing Emojis, you can switch back to the regular "
2141-"keyboard. If you can't see the Emoji keyboard, make sure you've [activated "
2142-"it in the Settings app](#how-can-i-add-a-new-keyboard-language)."
2143+"[Learn wow to switch between keyboard "
2144+"languages](#how-can-i-switch-between-keyboard-languages) to alternate "
2145+"between the regular keyboard and the Emoji keyboard. Once done with typing "
2146+"Emojis, you can switch back to the regular keyboard. If you can't see the "
2147+"Emoji keyboard, make sure you've [activated it in the Settings "
2148+"app](#how-can-i-add-a-new-keyboard-language)."
2149 msgstr ""
2150
2151-#. !!T
2152+#. type: Title ###
2153 #: content/pages/ui.md:48
2154 #, no-wrap
2155 msgid ""
2156-"What is the round circle on the welcome screen for? What does it show? Can I"
2157-" configure it?"
2158+"What is the round circle on the welcome screen for? What does it show? Can I "
2159+"configure it? !!T"
2160 msgstr ""
2161
2162 #. type: Plain text
2163 #: content/pages/ui.md:49
2164 msgid ""
2165-"The round circle is the infographic. It hows you recent phone activity, like"
2166-" the number of messages received or the number of songs played. You can "
2167+"The round circle is the infographic. It hows you recent phone activity, like "
2168+"the number of messages received or the number of songs played. You can "
2169 "disable it by launching the *Settings* app, navigating to *Security and "
2170 "privacy* and unticking *Stats on Welcome screen*."
2171 msgstr ""
2172
2173 #. type: Plain text
2174 #: content/pages/apps.md:2
2175-msgid "Apps"
2176+msgid "Title: Apps"
2177 msgstr ""
2178
2179 #. type: Plain text
2180@@ -688,24 +686,24 @@
2181 msgid "The Store"
2182 msgstr ""
2183
2184-#. !!T
2185+#. type: Title ###
2186 #: content/pages/apps.md:10
2187 #, no-wrap
2188-msgid "How do I find and install new scopes and applications?"
2189+msgid "How do I find and install new scopes and applications? !!T"
2190 msgstr ""
2191
2192 #. type: Plain text
2193 #: content/pages/apps.md:12
2194 msgid ""
2195 "From the Apps scope, you can either tap on the “search” icon on the right "
2196-"and start searching by name, or you can go all the way down in the scope and"
2197-" tap on the Ubuntu Store icon."
2198+"and start searching by name, or you can go all the way down in the scope and "
2199+"tap on the Ubuntu Store icon."
2200 msgstr ""
2201
2202-#. !!T
2203+#. type: Title ###
2204 #: content/pages/apps.md:13
2205 #, no-wrap
2206-msgid "How can I browse the store from my PC?"
2207+msgid "How can I browse the store from my PC? !!T"
2208 msgstr ""
2209
2210 #. type: Plain text
2211@@ -716,19 +714,19 @@
2212 "store](https://appstore.bhdouglass.com/apps)."
2213 msgstr ""
2214
2215-#. !!T
2216+#. type: Title ###
2217 #: content/pages/apps.md:16
2218 #, no-wrap
2219-msgid "How do I remove scopes and applications?"
2220+msgid "How do I remove scopes and applications? !!T"
2221 msgstr ""
2222
2223 #. type: Plain text
2224 #: content/pages/apps.md:18
2225 msgid ""
2226-"Search for the scope or application you wish to remove inside the store. "
2227-"Open it and press the *Uninstall* button to remove the application. "
2228-"Alternatively, for applications you can also long-press their icons on the "
2229-"dash to show their store page and the *Uninstall* button."
2230+"Search for the scope or application you wish to remove inside the "
2231+"store. Open it and press the *Uninstall* button to remove the "
2232+"application. Alternatively, for applications you can also long-press their "
2233+"icons on the dash to show their store page and the *Uninstall* button."
2234 msgstr ""
2235
2236 #. type: Title ##
2237@@ -737,10 +735,10 @@
2238 msgid "Misc"
2239 msgstr ""
2240
2241-#. !!T
2242+#. type: Title ###
2243 #: content/pages/apps.md:21
2244 #, no-wrap
2245-msgid "Do you have Spotify?"
2246+msgid "Do you have Spotify? !!T"
2247 msgstr ""
2248
2249 #. type: Plain text
2250@@ -750,10 +748,10 @@
2251 "([video](https://www.youtube.com/watch?v=ea90rwK_VuI))."
2252 msgstr ""
2253
2254-#. !!T
2255+#. type: Title ###
2256 #: content/pages/apps.md:25
2257 #, no-wrap
2258-msgid "Do you have Google Authenticator?"
2259+msgid "Do you have Google Authenticator? !!T"
2260 msgstr ""
2261
2262 #. type: Plain text
2263@@ -767,27 +765,27 @@
2264 msgid "Music"
2265 msgstr ""
2266
2267-#. !!T
2268+#. type: Title ###
2269 #: content/pages/apps.md:30
2270 #, no-wrap
2271-msgid "How do I add music to my device?"
2272+msgid "How do I add music to my device? !!T"
2273 msgstr ""
2274
2275 #. type: Plain text
2276 #: content/pages/apps.md:32
2277 msgid ""
2278 "You can add music in multiple ways. If you have pre-existing music files, "
2279-"simply connect your phone to your pc via the usb cable. Next, copy the music"
2280-" you wish to listen to to the *Music* folder. Your music will appear in the "
2281+"simply connect your phone to your pc via the usb cable. Next, copy the music "
2282+"you wish to listen to to the *Music* folder. Your music will appear in the "
2283 "music app. Alternatively, you can acquire music directly using the device "
2284 "via a scope, such as grooveshark or by downloading via the browser or "
2285 "another application."
2286 msgstr ""
2287
2288-#. !!T
2289+#. type: Title ###
2290 #: content/pages/apps.md:33
2291 #, no-wrap
2292-msgid "What music formats are supported?"
2293+msgid "What music formats are supported? !!T"
2294 msgstr ""
2295
2296 #. type: Plain text
2297@@ -795,10 +793,10 @@
2298 msgid "The music app supports OGG, FLAG and MP3 formats."
2299 msgstr ""
2300
2301-#. !!T
2302+#. type: Title ###
2303 #: content/pages/apps.md:36
2304 #, no-wrap
2305-msgid "How do I listen to podcasts? !"
2306+msgid "How do I listen to podcasts? !!!T"
2307 msgstr ""
2308
2309 #. type: Plain text
2310@@ -814,20 +812,20 @@
2311 msgid "Contacts"
2312 msgstr ""
2313
2314-#. !!T
2315+#. type: Title ###
2316 #: content/pages/apps.md:41
2317 #, no-wrap
2318-msgid "How can I sync my Google contacts to my device?"
2319+msgid "How can I sync my Google contacts to my device? !!T"
2320 msgstr ""
2321
2322 #. type: Plain text
2323 #: content/pages/apps.md:43
2324 msgid ""
2325-"The first time you open the Contacts app you’ll be asked if you want to sync"
2326-" contacts with your Google account. If you have answered “no” but change "
2327-"your mind later, you can do so by going to the Today scope, and setting up "
2328-"your Google account there. After that you can sync your contacts (and, if "
2329-"you want, calendar events as well)."
2330+"The first time you open the Contacts app you’ll be asked if you want to sync "
2331+"contacts with your Google account. If you have answered “no” but change your "
2332+"mind later, you can do so by going to the Today scope, and setting up your "
2333+"Google account there. After that you can sync your contacts (and, if you "
2334+"want, calendar events as well)."
2335 msgstr ""
2336
2337 #. type: Title ##
2338@@ -836,10 +834,10 @@
2339 msgid "Gallery"
2340 msgstr ""
2341
2342-#. !!T
2343+#. type: Title ###
2344 #: content/pages/apps.md:46
2345 #, no-wrap
2346-msgid "How can I share photos?"
2347+msgid "How can I share photos? !!T"
2348 msgstr ""
2349
2350 #. type: Plain text
2351@@ -851,19 +849,19 @@
2352 "you wish to share your photo."
2353 msgstr ""
2354
2355-#. !!T
2356+#. type: Title ###
2357 #: content/pages/apps.md:49
2358 #, no-wrap
2359-msgid "How can I share videos?"
2360+msgid "How can I share videos? !!T"
2361 msgstr ""
2362
2363 #. type: Plain text
2364 #: content/pages/apps.md:51
2365 msgid ""
2366-"If you've just recorded a video, share it easily by swiping to the left from"
2367-" the right edge inside the Camera app. Alternatively, use the Gallery app to"
2368-" find the video. Once loaded, select *Share* from the menu and choose how "
2369-"you wish to share your video."
2370+"If you've just recorded a video, share it easily by swiping to the left from "
2371+"the right edge inside the Camera app. Alternatively, use the Gallery app to "
2372+"find the video. Once loaded, select *Share* from the menu and choose how you "
2373+"wish to share your video."
2374 msgstr ""
2375
2376 #. type: Title ##
2377@@ -872,10 +870,10 @@
2378 msgid "Camera"
2379 msgstr ""
2380
2381-#. !!T
2382+#. type: Title ###
2383 #: content/pages/apps.md:54
2384 #, no-wrap
2385-msgid "How can I take a picture?"
2386+msgid "How can I take a picture? !!T"
2387 msgstr ""
2388
2389 #. type: Plain text
2390@@ -885,31 +883,31 @@
2391 "bottom edge of the phone for additional options. Enjoy taking your picture!"
2392 msgstr ""
2393
2394-#. !!T
2395+#. type: Title ###
2396 #: content/pages/apps.md:57
2397 #, no-wrap
2398-msgid "How can I crop / rotate a picture?"
2399+msgid "How can I crop / rotate a picture? !!T"
2400 msgstr ""
2401
2402 #. type: Plain text
2403 #: content/pages/apps.md:59
2404 msgid ""
2405-"Use the gallery app to select your picture. Select the *Edit* button next to"
2406-" the menu. Inside you'll find options to crop and rotate your picture."
2407+"Use the gallery app to select your picture. Select the *Edit* button next to "
2408+"the menu. Inside you'll find options to crop and rotate your picture."
2409 msgstr ""
2410
2411-#. !!T
2412+#. type: Title ###
2413 #: content/pages/apps.md:60
2414 #, no-wrap
2415-msgid "How can I record video?"
2416+msgid "How can I record video? !!T"
2417 msgstr ""
2418
2419 #. type: Plain text
2420 #: content/pages/apps.md:62
2421 msgid ""
2422-"Select the Camera app from the launcher or apps scope. Select the video icon"
2423-" on the bottom of the screen. Swipe up from the bottom edge of the phone for"
2424-" additional options. Enjoy taking your video!"
2425+"Select the Camera app from the launcher or apps scope. Select the video icon "
2426+"on the bottom of the screen. Swipe up from the bottom edge of the phone for "
2427+"additional options. Enjoy taking your video!"
2428 msgstr ""
2429
2430 #. type: Title ##
2431@@ -918,10 +916,10 @@
2432 msgid "Clock"
2433 msgstr ""
2434
2435-#. !!T
2436+#. type: Title ###
2437 #: content/pages/apps.md:65
2438 #, no-wrap
2439-msgid "How do I set an alarm?"
2440+msgid "How do I set an alarm? !!T"
2441 msgstr ""
2442
2443 #. type: Plain text
2444@@ -943,10 +941,10 @@
2445 msgid "HERE Maps"
2446 msgstr ""
2447
2448-#. !!T
2449+#. type: Title ###
2450 #: content/pages/apps.md:71
2451 #, no-wrap
2452-msgid "How can I get directions?"
2453+msgid "How can I get directions? !!T"
2454 msgstr ""
2455
2456 #. type: Plain text
2457@@ -956,10 +954,10 @@
2458 "*Directions*. Enter your destination and tap the *Get Directions* button."
2459 msgstr ""
2460
2461-#. !!T
2462+#. type: Title ###
2463 #: content/pages/apps.md:74
2464 #, no-wrap
2465-msgid "Can I navigate offline?"
2466+msgid "Can I navigate offline? !!T"
2467 msgstr ""
2468
2469 #. type: Plain text
2470@@ -967,10 +965,10 @@
2471 msgid "Unfortunately navigation requires an active connection."
2472 msgstr ""
2473
2474-#. !!T
2475+#. type: Title ###
2476 #: content/pages/apps.md:77
2477 #, no-wrap
2478-msgid "Can I view the map offline?"
2479+msgid "Can I view the map offline? !!T"
2480 msgstr ""
2481
2482 #. type: Plain text
2483@@ -982,7 +980,7 @@
2484
2485 #. type: Plain text
2486 #: content/pages/get-in-touch.md:2
2487-msgid "Get in touch"
2488+msgid "Title: Get in touch"
2489 msgstr ""
2490
2491 #. type: Plain text
2492@@ -1007,7 +1005,7 @@
2493
2494 #. type: Plain text
2495 #: content/pages/scopes.md:2
2496-msgid "Scopes"
2497+msgid "Title: Scopes"
2498 msgstr ""
2499
2500 #. type: Plain text
2501@@ -1016,25 +1014,25 @@
2502 msgid "*Curious about scopes?*\n"
2503 msgstr ""
2504
2505-#. !!T
2506+#. type: Title ###
2507 #: content/pages/scopes.md:7
2508 #, no-wrap
2509-msgid "How do favorites work?"
2510+msgid "How do favorites work? !!T"
2511 msgstr ""
2512
2513 #. type: Plain text
2514 #: content/pages/scopes.md:9
2515 msgid ""
2516-"Swipe up from the bottom edge of the dash to reveal a scopes manager. "
2517-"Favorite scopes you wish to appear on your dash by selecting them. Selecting"
2518-" them again will remove the favorite. All favorited scopes will appear on "
2519-"your dash."
2520+"Swipe up from the bottom edge of the dash to reveal a scopes "
2521+"manager. Favorite scopes you wish to appear on your dash by selecting "
2522+"them. Selecting them again will remove the favorite. All favorited scopes "
2523+"will appear on your dash."
2524 msgstr ""
2525
2526-#. !!T
2527+#. type: Title ###
2528 #: content/pages/scopes.md:10
2529 #, no-wrap
2530-msgid "How do I add new scopes?"
2531+msgid "How do I add new scopes? !!T"
2532 msgstr ""
2533
2534 #. type: Plain text
2535@@ -1046,10 +1044,10 @@
2536 "store button in the upper right to look for it in the ubuntu store."
2537 msgstr ""
2538
2539-#. !!T
2540+#. type: Title ###
2541 #: content/pages/scopes.md:13
2542 #, no-wrap
2543-msgid "How do I remove a scope?"
2544+msgid "How do I remove a scope? !!T"
2545 msgstr ""
2546
2547 #. type: Plain text
2548
2549=== added directory 'static'
2550=== renamed directory 'app' => 'static/app'
2551=== renamed file 'edit-here/index.html' => 'static/index.html'
2552=== renamed directory 'edit-here/themes' => 'static/themes'
2553=== renamed directory 'edit-here/themes/phone' => 'static/themes/app'

Subscribers

People subscribed via source and target branches