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
=== modified file '.bzrignore'
--- .bzrignore 2015-03-18 06:57:45 +0000
+++ .bzrignore 2015-03-19 17:24:01 +0000
@@ -1,8 +1,7 @@
1app/www1build/
2*.click2*.click
3*.user3*.user
4edit-here/cache4cache
5edit-here/backup5backup
6edit-here/po/en_US.po6po/en_US.po
7edit-here/content/pages/*.*.md7content/pages/*.*.md
8web/
98
=== modified file 'HACKING'
--- HACKING 2015-03-18 10:37:42 +0000
+++ HACKING 2015-03-19 17:24:01 +0000
@@ -50,22 +50,24 @@
50Directory structure50Directory structure
51-------------------51-------------------
5252
5353.
54├── app ← Everything related to the devices app.54├── content ← Here is the place to edit the content.
55│   └── www ← The viewable content of the app.55│ ├── images ← Images to be used in the docs.
56├── edit-here56│ └── pages ← Actual text to be edited.
57│   ├── content57├── debian ← Everything related to .deb packaging.
58│   │   └── pages ← Here is the place to edit the content.58├── internals ← Build tools, pelican config.
59│   ├── po ← Translations.59│ ├── tests ← Code for automated testing.
60│   ├── tests ← Code for automated testing goes here.60│ └── translations ← Python code for handling translations.
61│  └── themes ← Themes files, both templates and css/js.61├── po ← Translations (.pot and po files).
62│      ├── phone ← Phone/device app theme.62└── static
63│      └── web ← Online build (.ubuntu.com) theme.63 ├── app ← Static files for the click app.
64└── web ← The viewable content of of the online build. 64 └── themes ← Themes files, both templates and css/js.
6565 ├── app
66Note: Everything in ./app/www and ./web/ is automatically generated.66 └── web
67 Edit content in ./edit-here/content/pages/ and edit themes in67
68 ./edit-here/themes/.68Note: Everything in .build is automatically generated.
69 Edit content in ./content/pages/ and edit themes in
70 ./static/themes/.
6971
7072
71Prerequisites73Prerequisites
@@ -96,7 +98,7 @@
96This app is structured in a very simple way. All the content is and all your98This app is structured in a very simple way. All the content is and all your
97edits happen in99edits happen in
98100
99 ./edit-here/content/pages/101 ./content/pages/
100102
101The markup for the text is Markdown, which is very easy to learn. Just have 103The markup for the text is Markdown, which is very easy to learn. Just have
102a look at http://daringfireball.net/projects/markdown/ and some of the 104a look at http://daringfireball.net/projects/markdown/ and some of the
@@ -104,18 +106,18 @@
104106
105Once you're happy with your edits, change to the top-level directory and run107Once you're happy with your edits, change to the top-level directory and run
106108
107 make html109 make app
108110
109This will also generate translated pages.111This will also generate translated pages.
110112
111You can find the updated HTML in ./app/www/.113You can find the updated HTML in ./build/app/www/.
112114
113To launch the app, you can use ubuntu-html5-app-launcher in the www dir, or115To launch the app, you can use ubuntu-html5-app-launcher in the www dir, or
114just call116just call
115117
116 make launch118 make launch
117119
118(This will also run the 'make html' command for you.)120(This will also run the 'make app' command for you.)
119121
120122
121Creating a click123Creating a click
@@ -137,7 +139,7 @@
137139
138 make web140 make web
139141
140Find the resulting files in ./web in the top-level directory.142Find the resulting files in the ./build/web directory.
141143
142We plan to make use of 144We plan to make use of
143http://www.w3.org/International/questions/qa-apache-lang-neg for publishing145http://www.w3.org/International/questions/qa-apache-lang-neg for publishing
@@ -148,7 +150,7 @@
148150
149We love automated testing! We added a couple of test cases to 151We love automated testing! We added a couple of test cases to
150152
151 ./edit-here/tests/153 ./internals/tests/
152154
153and it would be great if you added more. We want to run these tests 155and it would be great if you added more. We want to run these tests
154whenever we build the package or publish the docs or whatever else. Just156whenever we build the package or publish the docs or whatever else. Just
@@ -162,7 +164,7 @@
162While you can just generate and install the click package manually,164While you can just generate and install the click package manually,
163we recommend the SDK for testing the phone app.165we recommend the SDK for testing the phone app.
164166
165You can run the `make html` target to generate the HTML files and then167You can run the `make appl` target to generate the HTML files and then
166open the app/help.ubuntuhtmlproject file with the Ubuntu SDK IDE.168open the app/help.ubuntuhtmlproject file with the Ubuntu SDK IDE.
167169
168From there, you can set up a desktop kit to run it on your host,170From there, you can set up a desktop kit to run it on your host,
@@ -177,7 +179,7 @@
177179
178 https://launchpad.net/help-app180 https://launchpad.net/help-app
179181
180To update the .pot file, simply run the following command in the edit-here/182To update the .pot file, simply run the following command in the top-level
181directory:183directory:
182184
183 make update-pot185 make update-pot
184186
=== removed file 'Makefile'
--- Makefile 2015-03-18 10:36:10 +0000
+++ Makefile 1970-01-01 00:00:00 +0000
@@ -1,28 +0,0 @@
1#!/usr/bin/make -f
2
3ignored := $(shell bzr ignored | cut -d' ' -f1)
4
5clean:
6ifneq ($(strip $(ignored)),)
7 $(foreach fn, $(ignored), $(shell rm -r $(fn);))
8endif
9
10check: clean
11 make -C edit-here check
12
13click: html
14 cd app && click build . && mv *.click ..
15
16html: clean
17 make -C edit-here html
18
19web: clean
20 make -C edit-here web
21
22update-pot:
23 cd edit-here && ./generate-pot
24
25launch:
26 make -C edit-here launch
27
28.PHONY: click html web update-pot clean check
290
=== renamed file 'edit-here/Makefile' => 'Makefile'
--- edit-here/Makefile 2015-03-18 10:36:10 +0000
+++ Makefile 2015-03-19 17:24:01 +0000
@@ -4,126 +4,74 @@
44
5BASEDIR=$(CURDIR)5BASEDIR=$(CURDIR)
6INPUTDIR=$(BASEDIR)/content6INPUTDIR=$(BASEDIR)/content
7OUTPUTDIR=$(BASEDIR)/../app/www7INTERNALS_DIR=$(BASEDIR)/internals
8CONFFILE=$(BASEDIR)/pelicanconf.py8PO_DIR=$(BASEDIR)/po
9PUBLISHCONF=$(BASEDIR)/publishconf.py9STATIC_DIR=$(BASEDIR)/static
1010
11OUTPUTDIR_WEB=$(BASEDIR)/../web11CONFFILE=$(INTERNALS_DIR)/pelicanconf.py
12THEMEDIR_WEB=$(BASEDIR)/themes/web12
1313BUILD_DIR=$(BASEDIR)/build
14FTP_HOST=localhost14APP_DIR=$(BUILD_DIR)/app
15FTP_USER=anonymous15OUTPUTDIR_APP=$(APP_DIR)/www
16FTP_TARGET_DIR=/16
1717OUTPUTDIR_WEB=$(BUILD_DIR)/web/www
18SSH_HOST=localhost18THEMEDIR_WEB=$(STATIC_DIR)/themes/web
19SSH_PORT=22
20SSH_USER=root
21SSH_TARGET_DIR=/var/www
22
23S3_BUCKET=my_s3_bucket
24
25CLOUDFILES_USERNAME=my_rackspace_username
26CLOUDFILES_API_KEY=my_rackspace_api_key
27CLOUDFILES_CONTAINER=my_cloudfiles_container
28
29DROPBOX_DIR=~/Dropbox/Public/
30
31GITHUB_PAGES_BRANCH=gh-pages
3219
33DEBUG ?= 020DEBUG ?= 0
34ifeq ($(DEBUG), 1)21ifeq ($(DEBUG), 1)
35 PELICANOPTS += -D22 PELICANOPTS += -D
36endif23endif
3724
38MD_FILES=$(wildcard content/pages/*.md)25MD_FILES=$(wildcard $(INPUTDIR)/pages/*.md)
39PO_FILES=$(wildcard po/*.po)26PO_FILES=$(wildcard $(PO_DIR)/*.po)
27
28ignored := $(shell bzr ignored | cut -d' ' -f1)
29
30clean:
31ifneq ($(strip $(ignored)),)
32 $(foreach fn, $(ignored), $(shell rm -r $(fn);))
33endif
4034
41help:35help:
42 @echo 'Makefile for a pelican Web site '36 @echo 'Makefile for a pelican Web site '
43 @echo ' '37 @echo ' '
44 @echo 'Usage: '38 @echo 'Usage: '
45 @echo ' make html (re)generate the web site '39 @echo ' make web (re)generate the (online) web site '
40 @echo ' make app (re)generate the (offline) content '
41 @echo ' for the phone app ("html" is an '
42 @echo ' alias) '
43 @echo ' make click generate click for the phone app '
44 @echo ' make launch launch the phone app '
45 @echo ' make translations generate translated markdown '
46 @echo ' make update-pot update the .pot file '
46 @echo ' make clean remove the generated files '47 @echo ' make clean remove the generated files '
47 @echo ' make regenerate regenerate files upon modification '
48 @echo ' make publish generate using production settings '
49 @echo ' make serve [PORT=8000] serve site at http://localhost:8000'
50 @echo ' make devserver [PORT=8000] start/restart develop_server.sh '
51 @echo ' make stopserver stop local server '
52 @echo ' make ssh_upload upload the web site via SSH '
53 @echo ' make rsync_upload upload the web site via rsync+ssh '
54 @echo ' make dropbox_upload upload the web site via Dropbox '
55 @echo ' make ftp_upload upload the web site via FTP '
56 @echo ' make s3_upload upload the web site via S3 '
57 @echo ' make cf_upload upload the web site via Cloud Files'
58 @echo ' make github upload the web site via gh-pages '
59 @echo ' '48 @echo ' '
60 @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'49 @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
61 @echo ' '50 @echo ' '
6251
63check:52check:
64 ./run-tests53 cd $(INTERNALS_DIR); ./run-tests
6554
66web:55translations:
67 ./generate-translations56 cd $(INTERNALS_DIR); ./generate-translations
68 $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_WEB) -s $(CONFFILE) $(PELICANOPTS) -t $(THEMEDIR_WEB)57
6958web: translations
70html:59 cd $(INTERNALS_DIR); $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_WEB) -s $(CONFFILE) $(PELICANOPTS) -t $(THEMEDIR_WEB)
71 ./generate-translations60
72 $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)61app: translations
73 cp index.html $(OUTPUTDIR)62 cd $(INTERNALS_DIR); $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR_APP) -s $(CONFFILE) $(PELICANOPTS)
7463 cp $(STATIC_DIR)/index.html $(OUTPUTDIR_APP)
75clean:64 cp $(STATIC_DIR)/app/* $(APP_DIR)
76 [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)65
7766html: app
78launch: html67
79 cd ../app/; `grep '^Exec' help.desktop | tail -1 | sed 's/^Exec=//' | sed 's/%.//'` &68click: app
8069 cd $(APP_DIR) && click build . && mv *.click $(BASEDIR)
81#regenerate:70
82# $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)71launch: app
83#72 cd $(APP_DIR); `grep '^Exec' help.desktop | tail -1 | sed 's/^Exec=//' | sed 's/%.//'` &
84#serve:73
85#ifdef PORT74update-pot:
86# cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)75 cd $(INTERNALS_DIR) && ./generate-pot
87#else76
88# cd $(OUTPUTDIR) && $(PY) -m pelican.server77.PHONY: html help clean web check launch click app update-pot translations
89#endif
90#
91#devserver:
92#ifdef PORT
93# $(BASEDIR)/develop_server.sh restart $(PORT)
94#else
95# $(BASEDIR)/develop_server.sh restart
96#endif
97#
98#stopserver:
99# kill -9 `cat pelican.pid`
100# kill -9 `cat srv.pid`
101# @echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
102#
103#publish:
104# $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
105#
106#ssh_upload: publish
107# scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
108#
109#rsync_upload: publish
110# rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
111#
112#dropbox_upload: publish
113# cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR)
114#
115#ftp_upload: publish
116# lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit"
117#
118#s3_upload: publish
119# s3cmd sync $(OUTPUTDIR)/ s3://$(S3_BUCKET) --acl-public --delete-removed --guess-mime-type
120#
121#cf_upload: publish
122# cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) .
123#
124#github: publish
125# ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $(OUTPUTDIR)
126# git push origin $(GITHUB_PAGES_BRANCH)
127#
128.PHONY: html help clean web check
129 #regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github
13078
=== renamed directory 'edit-here/content' => 'content'
=== modified file 'debian/help-app-web.docs'
--- debian/help-app-web.docs 2015-03-09 15:47:10 +0000
+++ debian/help-app-web.docs 2015-03-19 17:24:01 +0000
@@ -1,1 +1,1 @@
1web/*1build/web/www/*
22
=== removed directory 'edit-here'
=== removed file 'edit-here/develop_server.sh'
--- edit-here/develop_server.sh 2015-02-09 13:40:52 +0000
+++ edit-here/develop_server.sh 1970-01-01 00:00:00 +0000
@@ -1,103 +0,0 @@
1#!/usr/bin/env bash
2##
3# This section should match your Makefile
4##
5PY=${PY:-python}
6PELICAN=${PELICAN:-pelican}
7PELICANOPTS=
8
9BASEDIR=$(pwd)
10INPUTDIR=$BASEDIR/content
11OUTPUTDIR=$(BASEDIR)/../app/www
12CONFFILE=$BASEDIR/pelicanconf.py
13
14###
15# Don't change stuff below here unless you are sure
16###
17
18SRV_PID=$BASEDIR/srv.pid
19PELICAN_PID=$BASEDIR/pelican.pid
20
21function usage(){
22 echo "usage: $0 (stop) (start) (restart) [port]"
23 echo "This starts Pelican in debug and reload mode and then launches"
24 echo "an HTTP server to help site development. It doesn't read"
25 echo "your Pelican settings, so if you edit any paths in your Makefile"
26 echo "you will need to edit your settings as well."
27 exit 3
28}
29
30function alive() {
31 kill -0 $1 >/dev/null 2>&1
32}
33
34function shut_down(){
35 PID=$(cat $SRV_PID)
36 if [[ $? -eq 0 ]]; then
37 if alive $PID; then
38 echo "Stopping HTTP server"
39 kill $PID
40 else
41 echo "Stale PID, deleting"
42 fi
43 rm $SRV_PID
44 else
45 echo "HTTP server PIDFile not found"
46 fi
47
48 PID=$(cat $PELICAN_PID)
49 if [[ $? -eq 0 ]]; then
50 if alive $PID; then
51 echo "Killing Pelican"
52 kill $PID
53 else
54 echo "Stale PID, deleting"
55 fi
56 rm $PELICAN_PID
57 else
58 echo "Pelican PIDFile not found"
59 fi
60}
61
62function start_up(){
63 local port=$1
64 echo "Starting up Pelican and HTTP server"
65 shift
66 $PELICAN --debug --autoreload -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS &
67 pelican_pid=$!
68 echo $pelican_pid > $PELICAN_PID
69 cd $OUTPUTDIR
70 $PY -m pelican.server $port &
71 srv_pid=$!
72 echo $srv_pid > $SRV_PID
73 cd $BASEDIR
74 sleep 1
75 if ! alive $pelican_pid ; then
76 echo "Pelican didn't start. Is the Pelican package installed?"
77 return 1
78 elif ! alive $srv_pid ; then
79 echo "The HTTP server didn't start. Is there another service using port" $port "?"
80 return 1
81 fi
82 echo 'Pelican and HTTP server processes now running in background.'
83}
84
85###
86# MAIN
87###
88[[ ($# -eq 0) || ($# -gt 2) ]] && usage
89port=''
90[[ $# -eq 2 ]] && port=$2
91
92if [[ $1 == "stop" ]]; then
93 shut_down
94elif [[ $1 == "restart" ]]; then
95 shut_down
96 start_up $port
97elif [[ $1 == "start" ]]; then
98 if ! start_up $port; then
99 shut_down
100 fi
101else
102 usage
103fi
1040
=== removed file 'edit-here/fabfile.py'
--- edit-here/fabfile.py 2015-02-09 13:40:52 +0000
+++ edit-here/fabfile.py 1970-01-01 00:00:00 +0000
@@ -1,73 +0,0 @@
1from fabric.api import *
2import fabric.contrib.project as project
3import os
4import sys
5import SimpleHTTPServer
6import SocketServer
7
8# Local path configuration (can be absolute or relative to fabfile)
9env.deploy_path = '../app/www'
10DEPLOY_PATH = env.deploy_path
11
12# Remote server configuration
13production = 'root@localhost:22'
14dest_path = '/var/www'
15
16# Rackspace Cloud Files configuration settings
17env.cloudfiles_username = 'my_rackspace_username'
18env.cloudfiles_api_key = 'my_rackspace_api_key'
19env.cloudfiles_container = 'my_cloudfiles_container'
20
21
22def clean():
23 if os.path.isdir(DEPLOY_PATH):
24 local('rm -rf {deploy_path}'.format(**env))
25 local('mkdir {deploy_path}'.format(**env))
26
27def build():
28 local('pelican -s pelicanconf.py')
29
30def rebuild():
31 clean()
32 build()
33
34def regenerate():
35 local('pelican -r -s pelicanconf.py')
36
37def serve():
38 os.chdir(env.deploy_path)
39
40 PORT = 8000
41 class AddressReuseTCPServer(SocketServer.TCPServer):
42 allow_reuse_address = True
43
44 server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
45
46 sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
47 server.serve_forever()
48
49def reserve():
50 build()
51 serve()
52
53def preview():
54 local('pelican -s publishconf.py')
55
56def cf_upload():
57 rebuild()
58 local('cd {deploy_path} && '
59 'swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
60 '-U {cloudfiles_username} '
61 '-K {cloudfiles_api_key} '
62 'upload -c {cloudfiles_container} .'.format(**env))
63
64@hosts(production)
65def publish():
66 local('pelican -s publishconf.py')
67 project.rsync_project(
68 remote_dir=dest_path,
69 exclude=".DS_Store",
70 local_dir=DEPLOY_PATH.rstrip('/') + '/',
71 delete=True,
72 extra_opts='-c',
73 )
740
=== removed directory 'edit-here/local'
=== removed file 'edit-here/local/__init__.py'
=== removed file 'edit-here/publishconf.py'
--- edit-here/publishconf.py 2015-02-09 13:40:52 +0000
+++ edit-here/publishconf.py 1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*- #
3from __future__ import unicode_literals
4
5# This file is only used if you use `make publish` or
6# explicitly specify it as your config file.
7
8import os
9import sys
10sys.path.append(os.curdir)
11from pelicanconf import *
12
13SITEURL = ''
14RELATIVE_URLS = False
15
16FEED_ALL_ATOM = 'feeds/all.atom.xml'
17CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
18
19DELETE_OUTPUT_DIRECTORY = True
20
21# Following items are often useful when publishing
22
23#DISQUS_SITENAME = ""
24#GOOGLE_ANALYTICS = ""
250
=== added directory 'internals'
=== renamed file 'edit-here/__init__.py' => 'internals/__init__.py'
=== renamed file 'edit-here/generate-pot' => 'internals/generate-pot'
--- edit-here/generate-pot 2015-03-02 10:30:45 +0000
+++ internals/generate-pot 2015-03-19 17:24:01 +0000
@@ -5,7 +5,7 @@
55
6import sys6import sys
77
8from translations import Translations8from translations.build import Translations
99
1010
11def main():11def main():
1212
=== renamed file 'edit-here/generate-translations' => 'internals/generate-translations'
--- edit-here/generate-translations 2015-03-02 10:30:45 +0000
+++ internals/generate-translations 2015-03-19 17:24:01 +0000
@@ -4,7 +4,7 @@
44
5import sys5import sys
66
7from translations import Translations7from translations.build import Translations
88
99
10def main():10def main():
1111
=== renamed file 'edit-here/pelicanconf.py' => 'internals/pelicanconf.py'
--- edit-here/pelicanconf.py 2015-03-17 16:40:17 +0000
+++ internals/pelicanconf.py 2015-03-19 17:24:01 +0000
@@ -6,7 +6,9 @@
6SITENAME = u'Ubuntu for devices: Help'6SITENAME = u'Ubuntu for devices: Help'
7SITEURL = ''7SITEURL = ''
88
9PATH = 'content'9TOP_LEVEL_DIR = '../'
10PATH = TOP_LEVEL_DIR+'content/'
11TRANSLATIONS_DIR = TOP_LEVEL_DIR+'po/'
1012
11TIMEZONE = 'Europe/Paris'13TIMEZONE = 'Europe/Paris'
1214
@@ -17,9 +19,9 @@
1719
18SLUGIFY_SOURCE = 'basename'20SLUGIFY_SOURCE = 'basename'
19FILENAME_METADATA = '(?P<slug>\S+)\.(?P<lang>\S+)\.*'21FILENAME_METADATA = '(?P<slug>\S+)\.(?P<lang>\S+)\.*'
20PAGE_URL = ''22PAGE_URL = ''
21PAGE_SAVE_AS = ''23PAGE_SAVE_AS = ''
22PAGE_LANG_URL = '{slug}.{lang}.html'24PAGE_LANG_URL = '{slug}.{lang}.html'
23PAGE_LANG_SAVE_AS = '{slug}.{lang}.html'25PAGE_LANG_SAVE_AS = '{slug}.{lang}.html'
2426
25# Feed generation is usually not desired when developing27# Feed generation is usually not desired when developing
@@ -38,13 +40,13 @@
38DEFAULT_PAGINATION = False40DEFAULT_PAGINATION = False
3941
40# Uncomment following line if you want document-relative URLs when developing42# Uncomment following line if you want document-relative URLs when developing
41#RELATIVE_URLS = True43# RELATIVE_URLS = True
4244
43TAGS_SAVE_AS = ''45TAGS_SAVE_AS = ''
44TAG_SAVE_AS = ''46TAG_SAVE_AS = ''
45THEME = 'themes/phone'47THEME = TOP_LEVEL_DIR+'static/themes/app/'
4648
47MD_EXTENSIONS = ['local.q-and-a', 'attr_list', 'toc']49MD_EXTENSIONS = ['q-and-a', 'attr_list', 'toc']
4850
49META_TAGS = [51META_TAGS = [
50 '[TOC]',52 '[TOC]',
5153
=== renamed file 'edit-here/local/q-and-a.py' => 'internals/q-and-a.py'
=== renamed file 'edit-here/run-tests' => 'internals/run-tests'
--- edit-here/run-tests 2015-03-09 10:16:07 +0000
+++ internals/run-tests 2015-03-19 17:24:01 +0000
@@ -1,9 +1,12 @@
1#!/usr/bin/python31#!/usr/bin/python3
22
3import os
3import sys4import sys
4import unittest5import unittest
56
6test_directory = 'tests/'7from pelicanconf import TOP_LEVEL_DIR
8
9test_directory = os.path.join(TOP_LEVEL_DIR, 'internals/tests')
7test_filename = 'test_*'10test_filename = 'test_*'
8if len(sys.argv) > 1:11if len(sys.argv) > 1:
9 test_filename = sys.argv[1]12 test_filename = sys.argv[1]
1013
=== renamed directory 'edit-here/tests' => 'internals/tests'
=== modified file 'internals/tests/test_files.py'
--- edit-here/tests/test_files.py 2015-03-11 11:57:50 +0000
+++ internals/tests/test_files.py 2015-03-19 17:24:01 +0000
@@ -1,26 +1,31 @@
1import os1import os
2from unittest import TestCase2from unittest import TestCase
33
4import translations4from translations.build import Translations
5from translations import utils
56
67
7class HelpTestCase(TestCase):8class HelpTestCase(TestCase):
8 def __init__(self, *args):9 def __init__(self, *args):
9 self.translations = translations.Translations()10 self.translations = Translations()
10 TestCase.__init__(self, *args)11 TestCase.__init__(self, *args)
1112
12 def test_doc_files_size_not_0(self):13 def test_doc_files_size_not_0(self):
14 pwd = utils.use_top_level_dir()
13 sizes = [os.stat(fn).st_size15 sizes = [os.stat(fn).st_size
14 for fn in self.translations.documents.docs]16 for fn in self.translations.documents.docs]
15 self.assertNotIn(0, sizes)17 self.assertNotIn(0, sizes)
18 os.chdir(pwd)
1619
17 def test_po_files_size_not_0(self):20 def test_po_files_size_not_0(self):
21 pwd = utils.use_top_level_dir()
18 sizes = [os.stat(fn).st_size22 sizes = [os.stat(fn).st_size
19 for fn in self.translations.po.langs]23 for fn in self.translations.po.langs]
20 self.assertNotIn(0, sizes)24 self.assertNotIn(0, sizes)
25 os.chdir(pwd)
2126
22 def test_markdown_files(self):27 def test_markdown_files(self):
23 fns = self.translations.documents.find_docs()28 fns = self.translations.documents.find_docs()
24 checked_fns = [translations.verify_markdown_file(fn)29 checked_fns = [utils.verify_markdown_file(fn)
25 for fn in fns]30 for fn in fns]
26 self.assertEqual(len(fns), checked_fns.count(True))31 self.assertEqual(len(fns), checked_fns.count(True))
2732
=== modified file 'internals/tests/test_links.py'
--- edit-here/tests/test_links.py 2015-03-10 17:20:47 +0000
+++ internals/tests/test_links.py 2015-03-19 17:24:01 +0000
@@ -7,15 +7,19 @@
7from unittest import TestCase7from unittest import TestCase
8import urllib.parse8import urllib.parse
99
10from translations.utils import use_top_level_dir
11
1012
11def require_build(build):13def require_build(build):
12 tempdir = tempfile.mkdtemp()14 tempdir = tempfile.mkdtemp()
13 env = {}15 env = {}
14 if build == 'html':16 if build == 'app':
15 env = {'OUTPUTDIR': tempdir}17 env = {'OUTPUTDIR_APP': tempdir}
16 if build == 'web':18 if build == 'web':
17 env = {'OUTPUTDIR_WEB': tempdir}19 env = {'OUTPUTDIR_WEB': tempdir}
20 pwd = use_top_level_dir()
18 ret = subprocess.call(['make', '-es', build], env=env)21 ret = subprocess.call(['make', '-es', build], env=env)
22 os.chdir(pwd)
19 return (ret, tempdir)23 return (ret, tempdir)
2024
2125
2226
=== modified file 'internals/tests/test_translations.py'
--- edit-here/tests/test_translations.py 2015-03-17 09:24:48 +0000
+++ internals/tests/test_translations.py 2015-03-19 17:24:01 +0000
@@ -1,11 +1,11 @@
1from unittest import TestCase1from unittest import TestCase
22
3import translations3from translations.build import Translations
44
55
6class HelpTestCase(TestCase):6class HelpTestCase(TestCase):
7 def __init__(self, *args):7 def __init__(self, *args):
8 self.translations = translations.Translations()8 self.translations = Translations()
9 TestCase.__init__(self, *args)9 TestCase.__init__(self, *args)
1010
11 def test_first_line_of_docs_is_title_line(self):11 def test_first_line_of_docs_is_title_line(self):
1212
=== added directory 'internals/translations'
=== added file 'internals/translations/__init__.py'
=== added file 'internals/translations/build.py'
--- internals/translations/build.py 1970-01-01 00:00:00 +0000
+++ internals/translations/build.py 2015-03-19 17:24:01 +0000
@@ -0,0 +1,304 @@
1import codecs
2import glob
3import os
4import re
5import shutil
6import subprocess
7
8from translations.utils import (
9 find_bcp47_code,
10 full_path,
11 normalise_path,
12 require,
13 use_top_level_dir,
14 verify_markdown_file,
15)
16
17from translations.po4a import PO4A
18
19try:
20 import polib
21except ImportError:
22 require('python3-polib')
23
24from pelicanconf import (
25 HIDE_FROM_POT,
26 META_TAGS,
27 PATH,
28 TRANSLATIONS_DIR,
29)
30
31
32class POFile(object):
33 pofile = None
34
35 def __init__(self, po_fn):
36 self.po_fn = po_fn
37 self.load()
38
39 def load(self):
40 self.pofile = polib.pofile(full_path(self.po_fn))
41
42 def merge(self, pot_file_ob):
43 self.pofile.merge(pot_file_ob)
44
45 def save(self):
46 self.pofile.save(full_path(self.po_fn))
47
48 def find_in_msgid(self, find_str, translated=True, fuzzy=True,
49 untranslated=True):
50 entries = []
51 if translated:
52 entries += self.pofile.translated_entries()
53 if fuzzy:
54 entries += self.pofile.fuzzy_entries()
55 if untranslated:
56 entries += self.pofile.untranslated_entries()
57 results = []
58 for entry in entries:
59 if find_str in entry.msgid:
60 results += [entry]
61 return results
62
63 def hide_attr_list_statements(self):
64 entries = []
65 for statement in HIDE_FROM_POT:
66 entries.extend(self.find_in_msgid(statement))
67 statements = r'|'.join(HIDE_FROM_POT)
68 for entry in entries:
69 matches = re.findall(r'(.*?)\s*?(%s)\s*?' % statements,
70 entry.msgid)
71 # [('How do I update my system?', '!!T')]
72 if len(matches) == 1 and len(matches[0]) == 2:
73 entry.msgid = matches[0][0]
74 entry.comment = matches[0][1]
75 if matches[0][1] in entry.msgstr:
76 entry.msgstr = entry.msgstr.replace(' %s' % matches[0][1], '')
77 self.save()
78
79 def readd_attr_list_statements(self):
80 entries = []
81 for entry_group in [self.pofile.translated_entries(),
82 self.pofile.fuzzy_entries(),
83 self.pofile.untranslated_entries()]:
84 for entry in entry_group:
85 for statement in HIDE_FROM_POT:
86 if statement in entry.comment:
87 entries += [entry]
88 for entry in entries:
89 if not entry.msgid.endswith(entry.comment):
90 entry.msgid += ' %s' % entry.comment
91 if entry.msgstr and not entry.msgstr.endswith(entry.comment):
92 entry.msgstr += ' %s' % entry.comment
93 entry.comment = ''
94 self.save()
95
96 def safeguard_meta_tags(self):
97 for tag in META_TAGS:
98 for entry in self.find_in_msgid(tag):
99 if entry.msgid == tag:
100 entry.msgstr = entry.msgid
101 self.save()
102
103 def find_title_lines(self):
104 results = []
105 for entry in self.find_in_msgid('Title: '):
106 if entry.msgid.startswith('Title: '):
107 where = entry.occurrences[0][0]
108 first_line = codecs.open(full_path(where),
109 encoding='utf-8').readline().strip()
110 results += [(entry, first_line)]
111 return results
112
113 def replace_title_lines(self):
114 for entry, first_line in self.find_title_lines():
115 if entry.msgid != first_line:
116 print('Title line "%s" found, but not on the first line '
117 'of "%s".' % (entry.msgid, entry.linenum))
118 return False
119 entry.msgid = entry.msgid.replace('Title: ', '')
120 if self.po_fn.endswith('.po'):
121 entry.msgstr = ''
122 self.save()
123 return True
124
125 def find_link_in_markdown_message(self, entry):
126 link_regex = r'\[.+?\]\(\{filename\}(.+?)\).*?'
127 link_msgid = re.findall(link_regex, entry.msgid)[0]
128 link_msgstr = list(re.findall(link_regex, entry.msgstr))
129 return (link_msgid, link_msgstr)
130
131 def rewrite_links(self, documents, bcp47):
132 for entry in self.find_in_msgid('{filename}'):
133 (link_msgid, link_msgstr) = \
134 self.find_link_in_markdown_message(entry)
135 if [doc for doc in documents.docs if doc.endswith(link_msgid)]:
136 translated_doc_fn = os.path.basename(
137 documents.translated_doc_fn(link_msgid, bcp47))
138 if not link_msgstr:
139 entry.msgstr = entry.msgid
140 link_msgstr = [link_msgid]
141 entry.msgstr = entry.msgstr.replace(link_msgstr[0],
142 translated_doc_fn)
143 self.save()
144
145 def find_translated_title_line(self, original_title):
146 for entry in self.find_in_msgid(original_title):
147 if entry.msgid == original_title:
148 if entry.msgstr:
149 return entry.msgstr
150 return entry.msgid
151
152
153class PO(object):
154 def __init__(self, po4a):
155 self.fake_lang_code = 'en_US'
156 self.fake_po_fn = normalise_path(
157 os.path.join(TRANSLATIONS_DIR,
158 '%s.po' % self.fake_lang_code))
159 self.pot_fn = normalise_path(os.path.join(TRANSLATIONS_DIR,
160 'help.pot'))
161 self.pot_file_ob = POFile(self.pot_fn)
162 self.po4a = po4a
163 self.langs = {}
164 for po_fn in glob.glob(TRANSLATIONS_DIR+'/*.po'):
165 self.add_language(normalise_path(po_fn))
166
167 def add_language(self, po_fn):
168 gettext_code = os.path.basename(po_fn).split('.po')[0]
169 self.langs[po_fn] = {
170 'bcp47': find_bcp47_code(gettext_code),
171 'gettext_code': gettext_code,
172 'pofile': None,
173 }
174
175 def _remove_fake_po_file(self):
176 if os.path.exists(self.fake_po_fn):
177 os.remove(self.fake_po_fn)
178
179 def __del__(self):
180 self._remove_fake_po_file()
181
182 def load_pofile(self, po_fn):
183 if not self.langs[po_fn]['pofile']:
184 self.langs[po_fn]['pofile'] = POFile(po_fn)
185
186 def gettextize(self, documents):
187 if not self.po4a.gettextize(documents.docs, self.pot_fn):
188 return False
189 self.pot_file_ob.load()
190 return True
191
192 def generate_pot_file(self, documents):
193 if not self.gettextize(documents):
194 return False
195 if not self.pot_file_ob.replace_title_lines():
196 return False
197 self.pot_file_ob.hide_attr_list_statements()
198 for po_fn in self.langs:
199 self.load_pofile(po_fn)
200 self.langs[po_fn]['pofile'].merge(self.pot_file_ob.pofile)
201 if not self.langs[po_fn]['pofile'].replace_title_lines():
202 return False
203 self.langs[po_fn]['pofile'].hide_attr_list_statements()
204 return True
205
206 # we generate a fake translation for en-US which is going to be
207 # the default
208 def generate_fake_pofile(self):
209 pwd = use_top_level_dir()
210 self._remove_fake_po_file()
211 shutil.copy(self.pot_fn, self.fake_po_fn)
212 os.chdir(pwd)
213 self.add_language(self.fake_po_fn)
214
215 def find_translated_title_line(self, original_title, po_fn):
216 return self.langs[po_fn]['pofile'].find_translated_title_line(
217 original_title)
218
219 def rewrite_links(self, documents):
220 for po_fn in self.langs:
221 self.load_pofile(po_fn)
222 self.langs[po_fn]['pofile'].rewrite_links(
223 documents, self.langs[po_fn]['bcp47'])
224
225 def safeguard_meta_tags(self):
226 for po_fn in self.langs:
227 self.load_pofile(po_fn)
228 self.langs[po_fn]['pofile'].safeguard_meta_tags()
229
230
231class Documents(object):
232 def __init__(self):
233 self.docs = [fn for fn in self.find_docs()
234 if verify_markdown_file(fn)]
235
236 def find_docs(self):
237 docs = []
238 for dirpath, dirnames, fns in os.walk(PATH):
239 docs += [normalise_path(os.path.join(dirpath, fn))
240 for fn in fns
241 if fn.endswith('.md')]
242 return docs
243
244 def translated_doc_fn(self, fn, bcp47_code):
245 match = [doc for doc in self.docs
246 if os.path.basename(doc) == os.path.basename(fn)]
247 if not match:
248 return None
249 return '%s.%s.md' % (match[0].split('.md')[0],
250 bcp47_code)
251
252 def _call_po4a_translate(self, doc, po_fn, po4a):
253 res = po4a.translate(doc, po_fn)
254 return codecs.decode(res.communicate()[0])
255
256 def write_translated_markdown(self, po, po4a):
257 for po_fn in po.langs:
258 po.langs[po_fn]['pofile'].readd_attr_list_statements()
259 for doc_fn in self.docs:
260 output = self._call_po4a_translate(doc_fn, po_fn, po4a)
261 title_line = output.split('\n')[0].split('Title: ')[1]
262 translated_title_line = po.find_translated_title_line(
263 title_line, po_fn)
264 output = '\n'.join([line for line in output.split('\n')][1:])
265 new_path = full_path(self.translated_doc_fn(
266 doc_fn, po.langs[po_fn]['bcp47']))
267 text = "Title: %s\nDate:\n\n" % (translated_title_line)
268 text += output
269 if os.path.exists(new_path):
270 os.remove(new_path)
271 if not os.path.exists(os.path.dirname(new_path)):
272 os.makedirs(os.path.dirname(new_path))
273 with open(new_path, 'w', encoding='utf-8') as f:
274 f.write(text)
275 po.langs[po_fn]['pofile'].hide_attr_list_statements()
276
277
278class Translations(object):
279 def __init__(self):
280 self._cleanup()
281 self.documents = Documents()
282 self.po4a = PO4A()
283 self.po = PO(self.po4a)
284
285 def _cleanup(self):
286 r = subprocess.Popen(['bzr', 'ignored'], stdout=subprocess.PIPE)
287 fns = [full_path(f.split(' ')[0])
288 for f in codecs.decode(r.communicate()[0]).split('\n')
289 if f.strip() != '']
290 fns = [f for f in fns if os.path.exists(f)]
291 for f in fns:
292 try:
293 shutil.rmtree(f)
294 except NotADirectoryError:
295 os.remove(f)
296
297 def generate_pot_file(self):
298 return self.po.generate_pot_file(self.documents)
299
300 def generate_translations(self):
301 self.po.generate_fake_pofile()
302 self.po.rewrite_links(self.documents)
303 self.po.safeguard_meta_tags()
304 self.documents.write_translated_markdown(self.po, self.po4a)
0305
=== added file 'internals/translations/po4a.py'
--- internals/translations/po4a.py 1970-01-01 00:00:00 +0000
+++ internals/translations/po4a.py 2015-03-19 17:24:01 +0000
@@ -0,0 +1,62 @@
1import copy
2import os
3import shutil
4import subprocess
5
6from translations.utils import (
7 require,
8 use_top_level_dir,
9)
10
11# This defines how complete we expect translations to be before we
12# generate HTML from them. Needs to be string.
13TRANSLATION_COMPLETION_PERCENTAGE = '0'
14
15
16class PO4A(object):
17 pwd = None
18
19 def __init__(self):
20 self.default_args = [
21 '-f', 'text',
22 '-o', 'markdown',
23 '-M', 'utf-8',
24 ]
25 if not shutil.which('po4a'):
26 require('po4a')
27
28 def run(self, po4a_command, additional_args, with_output=False,
29 top_level_dir=False):
30 if top_level_dir:
31 self.pwd = use_top_level_dir()
32 args = copy.copy(self.default_args)
33 args += additional_args
34 if with_output:
35 ret = subprocess.Popen([po4a_command]+args, stdout=subprocess.PIPE)
36 else:
37 ret = subprocess.call([po4a_command]+args)
38 if top_level_dir:
39 os.chdir(self.pwd)
40 self.pwd = None
41 return ret
42
43 def gettextize(self, document_fns, pot_file):
44 args = copy.copy(self.default_args)
45 for document_fn in document_fns:
46 args += ['-m', document_fn]
47 args += [
48 '-p', pot_file,
49 '-L', 'utf-8',
50 ]
51 ret = self.run('po4a-gettextize', args, top_level_dir=True)
52 return (not ret)
53
54 def translate(self, doc, po_fn):
55 args = [
56 '-k', TRANSLATION_COMPLETION_PERCENTAGE,
57 '-m', doc,
58 '-p', po_fn,
59 '-L', 'utf-8',
60 ]
61 return self.run('po4a-translate', args, with_output=True,
62 top_level_dir=True)
063
=== renamed file 'edit-here/translations.py' => 'internals/translations/utils.py'
--- edit-here/translations.py 2015-03-17 09:24:48 +0000
+++ internals/translations/utils.py 2015-03-19 17:24:01 +0000
@@ -1,10 +1,5 @@
1import codecs1import codecs
2import copy
3import glob
4import os2import os
5import re
6import shutil
7import subprocess
8import sys3import sys
9import tempfile4import tempfile
105
@@ -15,11 +10,6 @@
15 sys.exit(1)10 sys.exit(1)
1611
17try:12try:
18 import polib
19except ImportError:
20 require('python3-polib')
21
22try:
23 import magic13 import magic
24except:14except:
25 require('python3-magic')15 require('python3-magic')
@@ -29,11 +19,10 @@
29except:19except:
30 require('python3-markdown')20 require('python3-markdown')
3121
32from pelicanconf import PATH, MD_EXTENSIONS, META_TAGS, HIDE_FROM_POT22from pelicanconf import (
3323 MD_EXTENSIONS,
34# This defines how complete we expect translations to be before we24 TOP_LEVEL_DIR,
35# generate HTML from them. Needs to be string.25)
36TRANSLATION_COMPLETION_PERCENTAGE = '0'
3726
38BCP47_OVERRIDES = (27BCP47_OVERRIDES = (
39 ('zh_CN', 'zh-hans'),28 ('zh_CN', 'zh-hans'),
@@ -47,6 +36,20 @@
47]36]
4837
4938
39def normalise_path(path):
40 return os.path.relpath(path, TOP_LEVEL_DIR)
41
42
43def full_path(path):
44 return os.path.abspath(os.path.join(TOP_LEVEL_DIR, path))
45
46
47def use_top_level_dir():
48 pwd = os.getcwd()
49 os.chdir(TOP_LEVEL_DIR)
50 return pwd
51
52
50def _temp_write_markdown(fn):53def _temp_write_markdown(fn):
51 (ret, tmp) = tempfile.mkstemp()54 (ret, tmp) = tempfile.mkstemp()
52 length = 055 length = 0
@@ -65,6 +68,7 @@
65def verify_markdown_file(fn):68def verify_markdown_file(fn):
66 ms = magic.open(magic.MAGIC_NONE)69 ms = magic.open(magic.MAGIC_NONE)
67 ms.load()70 ms.load()
71 fn = full_path(fn)
68 if ms.file(fn) not in MD_MAGIC_FILE_TYPES:72 if ms.file(fn) not in MD_MAGIC_FILE_TYPES:
69 return False73 return False
70 (ret, length) = _temp_write_markdown(fn)74 (ret, length) = _temp_write_markdown(fn)
@@ -78,314 +82,3 @@
78 return gettext_code.lower().replace('_', '-')82 return gettext_code.lower().replace('_', '-')
79 return [c[1] for c in BCP47_OVERRIDES83 return [c[1] for c in BCP47_OVERRIDES
80 if c[0] == gettext_code][0]84 if c[0] == gettext_code][0]
81
82
83class PO4A(object):
84 def __init__(self):
85 self.default_args = [
86 '-f', 'text',
87 '-o', 'markdown',
88 '-M', 'utf-8',
89 ]
90 if not shutil.which('po4a'):
91 require('po4a')
92
93 def run(self, po4a_command, additional_args, with_output=False):
94 args = copy.copy(self.default_args)
95 args += additional_args
96 if with_output:
97 ret = subprocess.Popen([po4a_command]+args, stdout=subprocess.PIPE)
98 else:
99 ret = subprocess.call([po4a_command]+args)
100 return ret
101
102 def gettextize(self, document_fns, pot_file):
103 args = copy.copy(self.default_args)
104 for document_fn in document_fns:
105 args += ['-m', document_fn]
106 args += [
107 '-p', pot_file,
108 '-L', 'utf-8',
109 ]
110 ret = self.run('po4a-gettextize', args)
111 return (not ret)
112
113 def translate(self, doc, po_fn):
114 args = [
115 '-k', TRANSLATION_COMPLETION_PERCENTAGE,
116 '-m', doc,
117 '-p', po_fn,
118 '-L', 'utf-8',
119 ]
120 return self.run('po4a-translate', args, with_output=True)
121
122
123class POFile(object):
124 def __init__(self, po_fn):
125 self.po_fn = po_fn
126 self.pofile = polib.pofile(po_fn)
127
128 def reread(self):
129 self.pofile = polib.pofile(self.po_fn)
130
131 def merge(self, pot_file_ob):
132 self.pofile.merge(pot_file_ob)
133
134 def save(self):
135 self.pofile.save(self.po_fn)
136
137 def find_in_msgid(self, find_str, translated=True, fuzzy=True,
138 untranslated=True):
139 entries = []
140 if translated:
141 entries += self.pofile.translated_entries()
142 if fuzzy:
143 entries += self.pofile.fuzzy_entries()
144 if untranslated:
145 entries += self.pofile.untranslated_entries()
146 results = []
147 for entry in entries:
148 if find_str in entry.msgid:
149 results += [entry]
150 return results
151
152 def hide_attr_list_statements(self):
153 entries = []
154 for statement in HIDE_FROM_POT:
155 entries.extend(self.find_in_msgid(statement))
156 statements = r'|'.join(HIDE_FROM_POT)
157 for entry in entries:
158 matches = re.findall(r'(.*?)\s*?(%s)\s*?' % statements,
159 entry.msgid)
160 # [('How do I update my system?', '!!T')]
161 if len(matches) == 1 and len(matches[0]) == 2:
162 entry.msgid = matches[0][0]
163 entry.comment = matches[0][1]
164 if matches[0][1] in entry.msgstr:
165 entry.msgstr = entry.msgstr.replace(' %s' % matches[0][1], '')
166 self.save()
167
168 def readd_attr_list_statements(self):
169 entries = []
170 for entry_group in [self.pofile.translated_entries(),
171 self.pofile.fuzzy_entries(),
172 self.pofile.untranslated_entries()]:
173 for entry in entry_group:
174 for statement in HIDE_FROM_POT:
175 if statement in entry.comment:
176 entries += [entry]
177 for entry in entries:
178 if not entry.msgid.endswith(entry.comment):
179 entry.msgid += ' %s' % entry.comment
180 if entry.msgstr and not entry.msgstr.endswith(entry.comment):
181 entry.msgstr += ' %s' % entry.comment
182 entry.comment = ''
183 self.save()
184
185 def safeguard_meta_tags(self):
186 for tag in META_TAGS:
187 for entry in self.find_in_msgid(tag):
188 if entry.msgid == tag:
189 entry.msgstr = entry.msgid
190 self.save()
191
192 def find_title_lines(self):
193 results = []
194 for entry in self.find_in_msgid('Title: '):
195 if entry.msgid.startswith('Title: '):
196 where = entry.occurrences[0][0]
197 first_line = codecs.open(where,
198 encoding='utf-8').readline().strip()
199 results += [(entry, first_line)]
200 return results
201
202 def replace_title_lines(self):
203 for entry, first_line in self.find_title_lines():
204 if entry.msgid != first_line:
205 print('Title line "%s" found, but not on the first line '
206 'of "%s".' % (entry.msgid, entry.linenum))
207 return False
208 entry.msgid = entry.msgid.replace('Title: ', '')
209 if self.po_fn.endswith('.po'):
210 entry.msgstr = ''
211 self.save()
212 return True
213
214 def find_link_in_markdown_message(self, entry):
215 link_regex = r'\[.+?\]\(\{filename\}(.+?)\).*?'
216 link_msgid = re.findall(link_regex, entry.msgid)[0]
217 link_msgstr = list(re.findall(link_regex, entry.msgstr))
218 return (link_msgid, link_msgstr)
219
220 def rewrite_links(self, documents, bcp47):
221 for entry in self.find_in_msgid('{filename}'):
222 (link_msgid, link_msgstr) = \
223 self.find_link_in_markdown_message(entry)
224 if [doc for doc in documents.docs if doc.endswith(link_msgid)]:
225 translated_doc_fn = os.path.basename(
226 documents.translated_doc_fn(link_msgid, bcp47))
227 if not link_msgstr:
228 entry.msgstr = entry.msgid
229 link_msgstr = [link_msgid]
230 entry.msgstr = entry.msgstr.replace(link_msgstr[0],
231 translated_doc_fn)
232 self.save()
233
234 def find_translated_title_line(self, original_title):
235 for entry in self.find_in_msgid(original_title):
236 if entry.msgid == original_title:
237 if entry.msgstr:
238 return entry.msgstr
239 return entry.msgid
240
241
242class PO(object):
243 def __init__(self, po4a):
244 self.translations_dir = os.path.abspath(os.path.join(PATH, '../po'))
245 self.fake_lang_code = 'en_US'
246 self.fake_po_fn = os.path.join(self.translations_dir,
247 '%s.po' % self.fake_lang_code)
248 self.pot_fn = os.path.join(self.translations_dir, 'help.pot')
249 self.pot_file_ob = POFile(self.pot_fn)
250 self.po4a = po4a
251 self.langs = {}
252 for po_fn in glob.glob(self.translations_dir+'/*.po'):
253 self.add_language(po_fn)
254
255 def add_language(self, po_fn):
256 gettext_code = os.path.basename(po_fn).split('.po')[0]
257 self.langs[po_fn] = {
258 'bcp47': find_bcp47_code(gettext_code),
259 'gettext_code': gettext_code,
260 'pofile': None,
261 }
262
263 def _remove_fake_po_file(self):
264 if os.path.exists(self.fake_po_fn):
265 os.remove(self.fake_po_fn)
266
267 def __del__(self):
268 self._remove_fake_po_file()
269
270 def load_pofile(self, po_fn):
271 if not self.langs[po_fn]['pofile']:
272 self.langs[po_fn]['pofile'] = POFile(po_fn)
273
274 def gettextize(self, documents):
275 if not self.po4a.gettextize(documents.docs, self.pot_fn):
276 return False
277 self.pot_file_ob.reread()
278 return True
279
280 def generate_pot_file(self, documents):
281 if not self.gettextize(documents):
282 return False
283 if not self.pot_file_ob.replace_title_lines():
284 return False
285 self.pot_file_ob.hide_attr_list_statements()
286 for po_fn in self.langs:
287 self.load_pofile(po_fn)
288 self.langs[po_fn]['pofile'].merge(self.pot_file_ob.pofile)
289 if not self.langs[po_fn]['pofile'].replace_title_lines():
290 return False
291 self.langs[po_fn]['pofile'].hide_attr_list_statements()
292 return True
293
294 # we generate a fake translation for en-US which is going to be
295 # the default
296 def generate_fake_pofile(self):
297 self._remove_fake_po_file()
298 shutil.copy(self.pot_fn, self.fake_po_fn)
299 self.add_language(self.fake_po_fn)
300
301 def find_translated_title_line(self, original_title, po_fn):
302 return self.langs[po_fn]['pofile'].find_translated_title_line(
303 original_title)
304
305 def rewrite_links(self, documents):
306 for po_fn in self.langs:
307 self.load_pofile(po_fn)
308 self.langs[po_fn]['pofile'].rewrite_links(
309 documents, self.langs[po_fn]['bcp47'])
310
311 def safeguard_meta_tags(self):
312 for po_fn in self.langs:
313 self.load_pofile(po_fn)
314 self.langs[po_fn]['pofile'].safeguard_meta_tags()
315
316
317class Documents(object):
318 def __init__(self):
319 self.docs = [fn for fn in self.find_docs()
320 if verify_markdown_file(fn)]
321
322 def find_docs(self):
323 docs = []
324 for dirpath, dirnames, fns in os.walk(PATH):
325 docs += [os.path.relpath(os.path.join(dirpath, fn),
326 os.path.join(PATH, '..'))
327 for fn in fns
328 if fn.endswith('.md')]
329 return docs
330
331 def translated_doc_fn(self, fn, bcp47_code):
332 match = [doc for doc in self.docs
333 if os.path.basename(doc) == os.path.basename(fn)]
334 if not match:
335 return None
336 return '%s.%s.md' % (match[0].split('.md')[0],
337 bcp47_code)
338
339 def _call_po4a_translate(self, doc, po_fn, po4a):
340 res = po4a.translate(doc, po_fn)
341 return codecs.decode(res.communicate()[0])
342
343 def write_translated_markdown(self, po, po4a):
344 for po_fn in po.langs:
345 po.langs[po_fn]['pofile'].readd_attr_list_statements()
346 for doc_fn in self.docs:
347 output = self._call_po4a_translate(doc_fn, po_fn, po4a)
348 title_line = output.split('\n')[0].split('Title: ')[1]
349 translated_title_line = po.find_translated_title_line(
350 title_line, po_fn)
351 output = '\n'.join([line for line in output.split('\n')][1:])
352 new_path = self.translated_doc_fn(doc_fn,
353 po.langs[po_fn]['bcp47'])
354 text = "Title: %s\nDate:\n\n" % (translated_title_line)
355 text += output
356 if os.path.exists(new_path):
357 os.remove(new_path)
358 if not os.path.exists(os.path.dirname(new_path)):
359 os.makedirs(os.path.dirname(new_path))
360 with open(new_path, 'w', encoding='utf-8') as f:
361 f.write(text)
362 po.langs[po_fn]['pofile'].hide_attr_list_statements()
363
364
365class Translations(object):
366 def __init__(self):
367 self._cleanup()
368 self.documents = Documents()
369 self.po4a = PO4A()
370 self.po = PO(self.po4a)
371
372 def _cleanup(self):
373 r = subprocess.Popen(['bzr', 'ignored'], stdout=subprocess.PIPE)
374 fns = [os.path.join(PATH, '../..', f.split(' ')[0])
375 for f in codecs.decode(r.communicate()[0]).split('\n')
376 if f.strip() != '']
377 fns = [f for f in fns if os.path.exists(f)]
378 for f in fns:
379 try:
380 shutil.rmtree(f)
381 except NotADirectoryError:
382 os.remove(f)
383
384 def generate_pot_file(self):
385 return self.po.generate_pot_file(self.documents)
386
387 def generate_translations(self):
388 self.po.generate_fake_pofile()
389 self.po.rewrite_links(self.documents)
390 self.po.safeguard_meta_tags()
391 self.documents.write_translated_markdown(self.po, self.po4a)
39285
=== renamed directory 'edit-here/po' => 'po'
=== modified file 'po/help.pot'
--- edit-here/po/help.pot 2015-03-19 11:16:04 +0000
+++ po/help.pot 2015-03-19 17:24:01 +0000
@@ -2,12 +2,12 @@
2# Copyright (C) YEAR Free Software Foundation, Inc.2# Copyright (C) YEAR Free Software Foundation, Inc.
3# This file is distributed under the same license as the PACKAGE package.3# This file is distributed under the same license as the PACKAGE package.
4# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.4# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5# 5#
6#, fuzzy6#, fuzzy
7msgid ""7msgid ""
8msgstr ""8msgstr ""
9"Project-Id-Version: PACKAGE VERSION\n"9"Project-Id-Version: PACKAGE VERSION\n"
10"POT-Creation-Date: 2015-03-19 12:15+0100\n"10"POT-Creation-Date: 2015-03-19 15:41+0100\n"
11"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"11"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"12"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Language-Team: LANGUAGE <LL@li.org>\n"13"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,7 +18,7 @@
1818
19#. type: Plain text19#. type: Plain text
20#: content/pages/security.md:220#: content/pages/security.md:2
21msgid "Security"21msgid "Title: Security"
22msgstr ""22msgstr ""
2323
24#. type: Plain text24#. type: Plain text
@@ -28,16 +28,14 @@
28msgstr ""28msgstr ""
2929
30#. type: Plain text30#. type: Plain text
31#: content/pages/security.md:6 content/pages/basic.md:631#: 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
32#: content/pages/settings.md:6 content/pages/ui.md:6 content/pages/apps.md:7
33#: content/pages/scopes.md:6
34msgid "[TOC]"32msgid "[TOC]"
35msgstr ""33msgstr ""
3634
37#. !!T35#. type: Title ###
38#: content/pages/security.md:736#: content/pages/security.md:7
39#, no-wrap37#, no-wrap
40msgid "How do I lock the phone?"38msgid "How do I lock the phone? !!T"
41msgstr ""39msgstr ""
4240
43#. type: Plain text41#. type: Plain text
@@ -49,10 +47,10 @@
49"& Privacy*, then *Phone Locking* to adjust the *Lock when idle* setting."47"& Privacy*, then *Phone Locking* to adjust the *Lock when idle* setting."
50msgstr ""48msgstr ""
5149
52#. !!T50#. type: Title ###
53#: content/pages/security.md:1051#: content/pages/security.md:10
54#, no-wrap52#, no-wrap
55msgid "How do I unlock the phone?"53msgid "How do I unlock the phone? !!T"
56msgstr ""54msgstr ""
5755
58#. type: Plain text56#. type: Plain text
@@ -62,10 +60,10 @@
62"enabled, you might be required to enter a pin or passcode."60"enabled, you might be required to enter a pin or passcode."
63msgstr ""61msgstr ""
6462
65#. !!T63#. type: Title ###
66#: content/pages/security.md:1364#: content/pages/security.md:13
67#, no-wrap65#, no-wrap
68msgid "How do I unlock the bootloader?"66msgid "How do I unlock the bootloader? !!T"
69msgstr ""67msgstr ""
7068
71#. type: Plain text69#. type: Plain text
@@ -75,10 +73,10 @@
75"related tasks, see the [developer site](http://developer.ubuntu.com/)"73"related tasks, see the [developer site](http://developer.ubuntu.com/)"
76msgstr ""74msgstr ""
7775
78#. !!T76#. type: Title ###
79#: content/pages/security.md:1677#: content/pages/security.md:16
80#, no-wrap78#, no-wrap
81msgid "How can I change my PIN/Passcode?"79msgid "How can I change my PIN/Passcode? !!T"
82msgstr ""80msgstr ""
8381
84#. type: Plain text82#. type: Plain text
@@ -88,12 +86,12 @@
88"*Security & Privacy* to adjust the *Lock when idle* setting."86"*Security & Privacy* to adjust the *Lock when idle* setting."
89msgstr ""87msgstr ""
9088
91#. !!T89#. type: Title ###
92#: content/pages/security.md:1990#: content/pages/security.md:19
93#, no-wrap91#, no-wrap
94msgid ""92msgid ""
95"Why do I have to type my PIN when using File Manager & Terminal (not default"93"Why do I have to type my PIN when using File Manager & Terminal (not default "
96" apps)?"94"apps)? !!T"
97msgstr ""95msgstr ""
9896
99#. type: Plain text97#. type: Plain text
@@ -103,11 +101,10 @@
103"pin/passcode is required. This is for your phone security."101"pin/passcode is required. This is for your phone security."
104msgstr ""102msgstr ""
105103
106#. !!T104#. type: Title ###
107#: content/pages/security.md:22105#: content/pages/security.md:22
108#, no-wrap106#, no-wrap
109msgid ""107msgid "How can I stop someone using the indicators when the phone is unlocked? !!T"
110"How can I stop someone using the indicators when the phone is unlocked?"
111msgstr ""108msgstr ""
112109
113#. type: Plain text110#. type: Plain text
@@ -119,10 +116,10 @@
119"settings* option."116"settings* option."
120msgstr ""117msgstr ""
121118
122#. !!T119#. type: Title ###
123#: content/pages/security.md:25120#: content/pages/security.md:25
124#, no-wrap121#, no-wrap
125msgid "I forgot my password or passcode. How can I unlock the phone?"122msgid "I forgot my password or passcode. How can I unlock the phone? !!T"
126msgstr ""123msgstr ""
127124
128#. type: Plain text125#. type: Plain text
@@ -132,7 +129,7 @@
132129
133#. type: Plain text130#. type: Plain text
134#: content/pages/basic.md:2131#: content/pages/basic.md:2
135msgid "Basic tasks"132msgid "Title: Basic tasks"
136msgstr ""133msgstr ""
137134
138#. type: Plain text135#. type: Plain text
@@ -141,37 +138,37 @@
141msgid "*If you are wondering how to perform basic tasks, look here.*\n"138msgid "*If you are wondering how to perform basic tasks, look here.*\n"
142msgstr ""139msgstr ""
143140
144#. !!T141#. type: Title ###
145#: content/pages/basic.md:7142#: content/pages/basic.md:7
146#, no-wrap143#, no-wrap
147msgid "How do I play music?"144msgid "How do I play music? !!T"
148msgstr ""145msgstr ""
149146
150#. type: Plain text147#. type: Plain text
151#: content/pages/basic.md:9148#: content/pages/basic.md:9
152msgid ""149msgid ""
153"The music app let's you play music copied to the device. In addition, "150"The music app let's you play music copied to the device. In addition, "
154"[scopes]({filename}scopes.md) such as 7digital and Grooveshark can also play"151"[scopes]({filename}scopes.md) such as 7digital and Grooveshark can also play "
155" music."152"music."
156msgstr ""153msgstr ""
157154
158#. !!T155#. type: Title ###
159#: content/pages/basic.md:10156#: content/pages/basic.md:10
160#, no-wrap157#, no-wrap
161msgid "How do I play videos?"158msgid "How do I play videos? !!T"
162msgstr ""159msgstr ""
163160
164#. type: Plain text161#. type: Plain text
165#: content/pages/basic.md:12162#: content/pages/basic.md:12
166msgid ""163msgid ""
167"The media player application will play videos copied to the device. You will"164"The media player application will play videos copied to the device. You will "
168" also find applications like youtube that give you streaming options."165"also find applications like youtube that give you streaming options."
169msgstr ""166msgstr ""
170167
171#. !!T168#. type: Title ###
172#: content/pages/basic.md:13169#: content/pages/basic.md:13
173#, no-wrap170#, no-wrap
174msgid "How do I take photos?"171msgid "How do I take photos? !!T"
175msgstr ""172msgstr ""
176173
177#. type: Plain text174#. type: Plain text
@@ -181,38 +178,38 @@
181"has both a front and rear camera, you can toggle which camera to use."178"has both a front and rear camera, you can toggle which camera to use."
182msgstr ""179msgstr ""
183180
184#. !!T181#. type: Title ###
185#: content/pages/basic.md:16182#: content/pages/basic.md:16
186#, no-wrap183#, no-wrap
187msgid "How do I take a screenshot?"184msgid "How do I take a screenshot? !!T"
188msgstr ""185msgstr ""
189186
190#. type: Plain text187#. type: Plain text
191#: content/pages/basic.md:18188#: content/pages/basic.md:18
192msgid ""189msgid ""
193"Press the Volume Up and Volume Down buttons at the same time until you see "190"Press the Volume Up and Volume Down buttons at the same time until you see "
194"the screen flashing. The screenshot will capture what is on your screen, and"191"the screen flashing. The screenshot will capture what is on your screen, and "
195" you can see the resulting picture in the Gallery app or the Photos scope."192"you can see the resulting picture in the Gallery app or the Photos scope."
196msgstr ""193msgstr ""
197194
198#. !!T195#. type: Title ###
199#: content/pages/basic.md:19196#: content/pages/basic.md:19
200#, no-wrap197#, no-wrap
201msgid "How do I see pictures I’ve taken?"198msgid "How do I see pictures I’ve taken? !!T"
202msgstr ""199msgstr ""
203200
204#. type: Plain text201#. type: Plain text
205#: content/pages/basic.md:21202#: content/pages/basic.md:21
206msgid ""203msgid ""
207"If you've just taken a picture, you can see it easily by swiping to the left"204"If you've just taken a picture, you can see it easily by swiping to the left "
208" from the right edge inside the camera app. Alternatively, use the gallery "205"from the right edge inside the camera app. Alternatively, use the gallery "
209"app to find the picture."206"app to find the picture."
210msgstr ""207msgstr ""
211208
212#. !!T209#. type: Title ###
213#: content/pages/basic.md:22210#: content/pages/basic.md:22
214#, no-wrap211#, no-wrap
215msgid "How do I record videos?"212msgid "How do I record videos? !!T"
216msgstr ""213msgstr ""
217214
218#. type: Plain text215#. type: Plain text
@@ -222,10 +219,10 @@
222"has both a front and rear camera, you can toggle which camera to use."219"has both a front and rear camera, you can toggle which camera to use."
223msgstr ""220msgstr ""
224221
225#. !!T222#. type: Title ###
226#: content/pages/basic.md:25223#: content/pages/basic.md:25
227#, no-wrap224#, no-wrap
228msgid "How can I send a text?"225msgid "How can I send a text? !!T"
229msgstr ""226msgstr ""
230227
231#. type: Plain text228#. type: Plain text
@@ -235,10 +232,10 @@
235"messages."232"messages."
236msgstr ""233msgstr ""
237234
238#. !!T235#. type: Title ###
239#: content/pages/basic.md:28236#: content/pages/basic.md:28
240#, no-wrap237#, no-wrap
241msgid "How do I make a call?"238msgid "How do I make a call? !!T"
242msgstr ""239msgstr ""
243240
244#. type: Plain text241#. type: Plain text
@@ -248,10 +245,10 @@
248"number."245"number."
249msgstr ""246msgstr ""
250247
251#. !!T248#. type: Title ###
252#: content/pages/basic.md:31249#: content/pages/basic.md:31
253#, no-wrap250#, no-wrap
254msgid "How do I check recently made/missed calls?"251msgid "How do I check recently made/missed calls? !!T"
255msgstr ""252msgstr ""
256253
257#. type: Plain text254#. type: Plain text
@@ -263,7 +260,7 @@
263260
264#. type: Plain text261#. type: Plain text
265#: content/pages/faq.md:2262#: content/pages/faq.md:2
266msgid "Get your questions answered."263msgid "Title: Get your questions answered."
267msgstr ""264msgstr ""
268265
269#. type: Plain text266#. type: Plain text
@@ -311,7 +308,7 @@
311308
312#. type: Plain text309#. type: Plain text
313#: content/pages/index.md:2310#: content/pages/index.md:2
314msgid "Welcome"311msgid "Title: Welcome"
315msgstr ""312msgstr ""
316313
317#. type: Plain text314#. type: Plain text
@@ -348,7 +345,7 @@
348345
349#. type: Plain text346#. type: Plain text
350#: content/pages/settings.md:2347#: content/pages/settings.md:2
351msgid "Settings"348msgid "Title: Settings"
352msgstr ""349msgstr ""
353350
354#. type: Plain text351#. type: Plain text
@@ -357,10 +354,10 @@
357msgid "*How do I change my phone settings?*\n"354msgid "*How do I change my phone settings?*\n"
358msgstr ""355msgstr ""
359356
360#. !!T357#. type: Title ###
361#: content/pages/settings.md:7358#: content/pages/settings.md:7
362#, no-wrap359#, no-wrap
363msgid "How do I update my system?"360msgid "How do I update my system? !!T"
364msgstr ""361msgstr ""
365362
366#. type: Plain text363#. type: Plain text
@@ -368,14 +365,14 @@
368msgid ""365msgid ""
369"Your device will prompt you when an update is ready. A notification will "366"Your device will prompt you when an update is ready. A notification will "
370"appear informing you of the new update. If you wish, you can manually check "367"appear informing you of the new update. If you wish, you can manually check "
371"and perform an update yourself. Open the *System Settings* application. "368"and perform an update yourself. Open the *System Settings* "
372"Select *Update*, and then click the check for updates button."369"application. Select *Update*, and then click the check for updates button."
373msgstr ""370msgstr ""
374371
375#. !!T372#. type: Title ###
376#: content/pages/settings.md:10373#: content/pages/settings.md:10
377#, no-wrap374#, no-wrap
378msgid "How do I set the time / language?"375msgid "How do I set the time / language? !!T"
379msgstr ""376msgstr ""
380377
381#. type: Plain text378#. type: Plain text
@@ -386,23 +383,24 @@
386"![Icon]({filename}/images/settings.gif)"383"![Icon]({filename}/images/settings.gif)"
387msgstr ""384msgstr ""
388385
389#. !!T386#. type: Title ###
390#: content/pages/settings.md:14387#: content/pages/settings.md:14
391#, no-wrap388#, no-wrap
392msgid "How can I change my wallpaper/background?"389msgid "How can I change my wallpaper/background? !!T"
393msgstr ""390msgstr ""
394391
395#. type: Plain text392#. type: Plain text
396#: content/pages/settings.md:16393#: content/pages/settings.md:16
397msgid ""394msgid ""
398"Open the *System Settings* application. Select the *Background* option. "395"Open the *System Settings* application. Select the *Background* "
399"Press the *Add Image* button and choice your image to set as a background."396"option. Press the *Add Image* button and choice your image to set as a "
397"background."
400msgstr ""398msgstr ""
401399
402#. !!T400#. type: Title ###
403#: content/pages/settings.md:17401#: content/pages/settings.md:17
404#, no-wrap402#, no-wrap
405msgid "How do I keep the screen on?"403msgid "How do I keep the screen on? !!T"
406msgstr ""404msgstr ""
407405
408#. type: Plain text406#. type: Plain text
@@ -412,26 +410,26 @@
412"option. Select the *Lock Phone* option, and then *Lock when idle*. St"410"option. Select the *Lock Phone* option, and then *Lock when idle*. St"
413msgstr ""411msgstr ""
414412
415#. !!T413#. type: Title ###
416#: content/pages/settings.md:20414#: content/pages/settings.md:20
417#, no-wrap415#, no-wrap
418msgid "How do I set up my accounts?"416msgid "How do I set up my accounts? !!T"
419msgstr ""417msgstr ""
420418
421#. type: Plain text419#. type: Plain text
422#: content/pages/settings.md:22420#: content/pages/settings.md:22
423msgid ""421msgid ""
424"You can set up some of your accounts from the scopes. Today scope allows you"422"You can set up some of your accounts from the scopes. Today scope allows you "
425" to configure your Google and Fitbit account, while the Pictures scope lets "423"to configure your Google and Fitbit account, while the Pictures scope lets "
426"you configure your flickr, Facebook and Instagram account. You can manage "424"you configure your flickr, Facebook and Instagram account. You can manage "
427"all your accounts (including social media, email, etc) from the *System "425"all your accounts (including social media, email, etc) from the *System "
428"Settings* app, under *Personal*, *Accounts*."426"Settings* app, under *Personal*, *Accounts*."
429msgstr ""427msgstr ""
430428
431#. !!T429#. type: Title ###
432#: content/pages/settings.md:23430#: content/pages/settings.md:23
433#, no-wrap431#, no-wrap
434msgid "How do I configure my notifications?"432msgid "How do I configure my notifications? !!T"
435msgstr ""433msgstr ""
436434
437#. type: Plain text435#. type: Plain text
@@ -443,10 +441,10 @@
443"notifications from any application on your device."441"notifications from any application on your device."
444msgstr ""442msgstr ""
445443
446#. !!T444#. type: Title ###
447#: content/pages/settings.md:26445#: content/pages/settings.md:26
448#, no-wrap446#, no-wrap
449msgid "How do I change the ringtone for calls and texts?"447msgid "How do I change the ringtone for calls and texts? !!T"
450msgstr ""448msgstr ""
451449
452#. type: Plain text450#. type: Plain text
@@ -460,36 +458,36 @@
460458
461#. type: Plain text459#. type: Plain text
462#: content/pages/ui.md:2460#: content/pages/ui.md:2
463msgid "User Interface"461msgid "Title: User Interface"
464msgstr ""462msgstr ""
465463
466#. type: Plain text464#. type: Plain text
467#: content/pages/ui.md:4465#: content/pages/ui.md:4
468#, no-wrap466#, no-wrap
469msgid ""467msgid ""
470"*Are you wondering about the dash, scopes, swiping? You've come to the right"468"*Are you wondering about the dash, scopes, swiping? You've come to the right "
471" place!*\n"469"place!*\n"
472msgstr ""470msgstr ""
473471
474#. !!T472#. type: Title ###
475#: content/pages/ui.md:7473#: content/pages/ui.md:7
476#, no-wrap474#, no-wrap
477msgid "What is the dash?"475msgid "What is the dash? !!T"
478msgstr ""476msgstr ""
479477
480#. type: Plain text478#. type: Plain text
481#: content/pages/ui.md:9479#: content/pages/ui.md:9
482msgid ""480msgid ""
483"The dash contains a list of applications installed on the device, along with"481"The dash contains a list of applications installed on the device, along with "
484" presenting the scopes and store. The dash is the first thing you see when "482"presenting the scopes and store. The dash is the first thing you see when "
485"booting the phone. You can switch to it again at any time by swiping left "483"booting the phone. You can switch to it again at any time by swiping left "
486"from the right screen edge."484"from the right screen edge."
487msgstr ""485msgstr ""
488486
489#. !!T487#. type: Title ###
490#: content/pages/ui.md:10488#: content/pages/ui.md:10
491#, no-wrap489#, no-wrap
492msgid "What is the launcher?"490msgid "What is the launcher? !!T"
493msgstr ""491msgstr ""
494492
495#. type: Plain text493#. type: Plain text
@@ -499,10 +497,10 @@
499"the launcher at any time by swiping right from the left screen edge."497"the launcher at any time by swiping right from the left screen edge."
500msgstr ""498msgstr ""
501499
502#. !!T500#. type: Title ###
503#: content/pages/ui.md:13501#: content/pages/ui.md:13
504#, no-wrap502#, no-wrap
505msgid "How can I customize the launcher?"503msgid "How can I customize the launcher? !!T"
506msgstr ""504msgstr ""
507505
508#. type: Plain text506#. type: Plain text
@@ -515,69 +513,68 @@
515"in."513"in."
516msgstr ""514msgstr ""
517515
518#. !!T516#. type: Title ###
519#: content/pages/ui.md:17517#: content/pages/ui.md:17
520#, no-wrap518#, no-wrap
521msgid "What are the indicators?"519msgid "What are the indicators? !!T"
522msgstr ""520msgstr ""
523521
524#. type: Plain text522#. type: Plain text
525#: content/pages/ui.md:19523#: content/pages/ui.md:19
526msgid ""524msgid ""
527"Indicators convey quick useful information about your device, like the time,"525"Indicators convey quick useful information about your device, like the time, "
528" data connection, location, sound, and notifications. You can access the "526"data connection, location, sound, and notifications. You can access the "
529"indicators at any time by swiping down from the top screen edge."527"indicators at any time by swiping down from the top screen edge."
530msgstr ""528msgstr ""
531529
532#. !!T530#. type: Title ###
533#: content/pages/ui.md:20531#: content/pages/ui.md:20
534#, no-wrap532#, no-wrap
535msgid "How do I switch applications?"533msgid "How do I switch applications? !!T"
536msgstr ""534msgstr ""
537535
538#. type: Plain text536#. type: Plain text
539#: content/pages/ui.md:23537#: content/pages/ui.md:23
540msgid ""538msgid ""
541"To switch applications, slide your finger left from the right edge of the "539"To switch applications, slide your finger left from the right edge of the "
542"screen. If you slide quickly you will cycle through each application. "540"screen. If you slide quickly you will cycle through each "
543"However, if you slide more slowly, an application switcher will appear "541"application. However, if you slide more slowly, an application switcher will "
544"allowing you to select the application you wish to switch to, including the "542"appear allowing you to select the application you wish to switch to, "
545"dash."543"including the dash."
546msgstr ""544msgstr ""
547545
548#. !!T546#. type: Title ###
549#: content/pages/ui.md:24547#: content/pages/ui.md:24
550#, no-wrap548#, no-wrap
551msgid "How do I close applications?"549msgid "How do I close applications? !!T"
552msgstr ""550msgstr ""
553551
554#. type: Plain text552#. type: Plain text
555#: content/pages/ui.md:26553#: content/pages/ui.md:26
556msgid ""554msgid ""
557"To close an application, slide your finger *slowly* left from the right edge"555"To close an application, slide your finger *slowly* left from the right edge "
558" of the screen. An application switcher will appear. Place your finger on "556"of the screen. An application switcher will appear. Place your finger on the "
559"the application preview you wish to close and swipe up or down. The "557"application preview you wish to close and swipe up or down. The application "
560"application will disappear."558"will disappear."
561msgstr ""559msgstr ""
562560
563#. !!T561#. type: Title ###
564#: content/pages/ui.md:27562#: content/pages/ui.md:27
565#, no-wrap563#, no-wrap
566msgid "How can I copy and paste?"564msgid "How can I copy and paste? !!T"
567msgstr ""565msgstr ""
568566
569#. type: Plain text567#. type: Plain text
570#: content/pages/ui.md:29568#: content/pages/ui.md:29
571msgid ""569msgid ""
572"For text that can be copied and pasted, press and hold the text in question."570"For text that can be copied and pasted, press and hold the text in "
573" A menu will appear allowing you to cut, copy and paste."571"question. A menu will appear allowing you to cut, copy and paste."
574msgstr ""572msgstr ""
575573
576#. !!T574#. type: Title ###
577#: content/pages/ui.md:30575#: content/pages/ui.md:30
578#, no-wrap576#, no-wrap
579msgid ""577msgid "What are the small characters on the keyboard and how can I select them? !!T"
580"What are the small characters on the keyboard and how can I select them?"
581msgstr ""578msgstr ""
582579
583#. type: Plain text580#. type: Plain text
@@ -588,10 +585,10 @@
588"numbers and accented characters. Give it a try!"585"numbers and accented characters. Give it a try!"
589msgstr ""586msgstr ""
590587
591#. !!T588#. type: Title ###
592#: content/pages/ui.md:33589#: content/pages/ui.md:33
593#, no-wrap590#, no-wrap
594msgid "The keyboard behaves funny. What can I do about it?"591msgid "The keyboard behaves funny. What can I do about it? !!T"
595msgstr ""592msgstr ""
596593
597#. type: Plain text594#. type: Plain text
@@ -603,10 +600,10 @@
603"control of the input."600"control of the input."
604msgstr ""601msgstr ""
605602
606#. !!T603#. type: Title ###
607#: content/pages/ui.md:36604#: content/pages/ui.md:36
608#, no-wrap605#, no-wrap
609msgid "How can I add a new keyboard language?"606msgid "How can I add a new keyboard language? !!T"
610msgstr ""607msgstr ""
611608
612#. type: Plain text609#. type: Plain text
@@ -616,14 +613,14 @@
616"that might not be available in other keyboards and (if available) use word "613"that might not be available in other keyboards and (if available) use word "
617"prediction and spell checking for that new language. Go to the Settings "614"prediction and spell checking for that new language. Go to the Settings "
618"app, tap on Language/Text and then on Keyboard layouts. From there, you can "615"app, tap on Language/Text and then on Keyboard layouts. From there, you can "
619"then tick the keyboard you want to add and then [use it](#how-can-i-switch-"616"then tick the keyboard you want to add and then [use "
620"between-keyboard-languages)"617"it](#how-can-i-switch-between-keyboard-languages)"
621msgstr ""618msgstr ""
622619
623#. !!T620#. type: Title ###
624#: content/pages/ui.md:40621#: content/pages/ui.md:40
625#, no-wrap622#, no-wrap
626msgid "How can I switch between keyboard languages?"623msgid "How can I switch between keyboard languages? !!T"
627msgstr ""624msgstr ""
628625
629#. type: Plain text626#. type: Plain text
@@ -636,42 +633,43 @@
636"activated it in the Settings app](#how-can-i-add-a-new-keyboard-language)."633"activated it in the Settings app](#how-can-i-add-a-new-keyboard-language)."
637msgstr ""634msgstr ""
638635
639#. !!T636#. type: Title ###
640#: content/pages/ui.md:44637#: content/pages/ui.md:44
641#, no-wrap638#, no-wrap
642msgid "How can I type Emoji icons?"639msgid "How can I type Emoji icons? !!T"
643msgstr ""640msgstr ""
644641
645#. type: Plain text642#. type: Plain text
646#: content/pages/ui.md:47643#: content/pages/ui.md:47
647msgid ""644msgid ""
648"[Learn wow to switch between keyboard languages](#how-can-i-switch-between-"645"[Learn wow to switch between keyboard "
649"keyboard-languages) to alternate between the regular keyboard and the Emoji "646"languages](#how-can-i-switch-between-keyboard-languages) to alternate "
650"keyboard. Once done with typing Emojis, you can switch back to the regular "647"between the regular keyboard and the Emoji keyboard. Once done with typing "
651"keyboard. If you can't see the Emoji keyboard, make sure you've [activated "648"Emojis, you can switch back to the regular keyboard. If you can't see the "
652"it in the Settings app](#how-can-i-add-a-new-keyboard-language)."649"Emoji keyboard, make sure you've [activated it in the Settings "
650"app](#how-can-i-add-a-new-keyboard-language)."
653msgstr ""651msgstr ""
654652
655#. !!T653#. type: Title ###
656#: content/pages/ui.md:48654#: content/pages/ui.md:48
657#, no-wrap655#, no-wrap
658msgid ""656msgid ""
659"What is the round circle on the welcome screen for? What does it show? Can I"657"What is the round circle on the welcome screen for? What does it show? Can I "
660" configure it?"658"configure it? !!T"
661msgstr ""659msgstr ""
662660
663#. type: Plain text661#. type: Plain text
664#: content/pages/ui.md:49662#: content/pages/ui.md:49
665msgid ""663msgid ""
666"The round circle is the infographic. It hows you recent phone activity, like"664"The round circle is the infographic. It hows you recent phone activity, like "
667" the number of messages received or the number of songs played. You can "665"the number of messages received or the number of songs played. You can "
668"disable it by launching the *Settings* app, navigating to *Security and "666"disable it by launching the *Settings* app, navigating to *Security and "
669"privacy* and unticking *Stats on Welcome screen*."667"privacy* and unticking *Stats on Welcome screen*."
670msgstr ""668msgstr ""
671669
672#. type: Plain text670#. type: Plain text
673#: content/pages/apps.md:2671#: content/pages/apps.md:2
674msgid "Apps"672msgid "Title: Apps"
675msgstr ""673msgstr ""
676674
677#. type: Plain text675#. type: Plain text
@@ -688,24 +686,24 @@
688msgid "The Store"686msgid "The Store"
689msgstr ""687msgstr ""
690688
691#. !!T689#. type: Title ###
692#: content/pages/apps.md:10690#: content/pages/apps.md:10
693#, no-wrap691#, no-wrap
694msgid "How do I find and install new scopes and applications?"692msgid "How do I find and install new scopes and applications? !!T"
695msgstr ""693msgstr ""
696694
697#. type: Plain text695#. type: Plain text
698#: content/pages/apps.md:12696#: content/pages/apps.md:12
699msgid ""697msgid ""
700"From the Apps scope, you can either tap on the “search” icon on the right "698"From the Apps scope, you can either tap on the “search” icon on the right "
701"and start searching by name, or you can go all the way down in the scope and"699"and start searching by name, or you can go all the way down in the scope and "
702" tap on the Ubuntu Store icon."700"tap on the Ubuntu Store icon."
703msgstr ""701msgstr ""
704702
705#. !!T703#. type: Title ###
706#: content/pages/apps.md:13704#: content/pages/apps.md:13
707#, no-wrap705#, no-wrap
708msgid "How can I browse the store from my PC?"706msgid "How can I browse the store from my PC? !!T"
709msgstr ""707msgstr ""
710708
711#. type: Plain text709#. type: Plain text
@@ -716,19 +714,19 @@
716"store](https://appstore.bhdouglass.com/apps)."714"store](https://appstore.bhdouglass.com/apps)."
717msgstr ""715msgstr ""
718716
719#. !!T717#. type: Title ###
720#: content/pages/apps.md:16718#: content/pages/apps.md:16
721#, no-wrap719#, no-wrap
722msgid "How do I remove scopes and applications?"720msgid "How do I remove scopes and applications? !!T"
723msgstr ""721msgstr ""
724722
725#. type: Plain text723#. type: Plain text
726#: content/pages/apps.md:18724#: content/pages/apps.md:18
727msgid ""725msgid ""
728"Search for the scope or application you wish to remove inside the store. "726"Search for the scope or application you wish to remove inside the "
729"Open it and press the *Uninstall* button to remove the application. "727"store. Open it and press the *Uninstall* button to remove the "
730"Alternatively, for applications you can also long-press their icons on the "728"application. Alternatively, for applications you can also long-press their "
731"dash to show their store page and the *Uninstall* button."729"icons on the dash to show their store page and the *Uninstall* button."
732msgstr ""730msgstr ""
733731
734#. type: Title ##732#. type: Title ##
@@ -737,10 +735,10 @@
737msgid "Misc"735msgid "Misc"
738msgstr ""736msgstr ""
739737
740#. !!T738#. type: Title ###
741#: content/pages/apps.md:21739#: content/pages/apps.md:21
742#, no-wrap740#, no-wrap
743msgid "Do you have Spotify?"741msgid "Do you have Spotify? !!T"
744msgstr ""742msgstr ""
745743
746#. type: Plain text744#. type: Plain text
@@ -750,10 +748,10 @@
750"([video](https://www.youtube.com/watch?v=ea90rwK_VuI))."748"([video](https://www.youtube.com/watch?v=ea90rwK_VuI))."
751msgstr ""749msgstr ""
752750
753#. !!T751#. type: Title ###
754#: content/pages/apps.md:25752#: content/pages/apps.md:25
755#, no-wrap753#, no-wrap
756msgid "Do you have Google Authenticator?"754msgid "Do you have Google Authenticator? !!T"
757msgstr ""755msgstr ""
758756
759#. type: Plain text757#. type: Plain text
@@ -767,27 +765,27 @@
767msgid "Music"765msgid "Music"
768msgstr ""766msgstr ""
769767
770#. !!T768#. type: Title ###
771#: content/pages/apps.md:30769#: content/pages/apps.md:30
772#, no-wrap770#, no-wrap
773msgid "How do I add music to my device?"771msgid "How do I add music to my device? !!T"
774msgstr ""772msgstr ""
775773
776#. type: Plain text774#. type: Plain text
777#: content/pages/apps.md:32775#: content/pages/apps.md:32
778msgid ""776msgid ""
779"You can add music in multiple ways. If you have pre-existing music files, "777"You can add music in multiple ways. If you have pre-existing music files, "
780"simply connect your phone to your pc via the usb cable. Next, copy the music"778"simply connect your phone to your pc via the usb cable. Next, copy the music "
781" you wish to listen to to the *Music* folder. Your music will appear in the "779"you wish to listen to to the *Music* folder. Your music will appear in the "
782"music app. Alternatively, you can acquire music directly using the device "780"music app. Alternatively, you can acquire music directly using the device "
783"via a scope, such as grooveshark or by downloading via the browser or "781"via a scope, such as grooveshark or by downloading via the browser or "
784"another application."782"another application."
785msgstr ""783msgstr ""
786784
787#. !!T785#. type: Title ###
788#: content/pages/apps.md:33786#: content/pages/apps.md:33
789#, no-wrap787#, no-wrap
790msgid "What music formats are supported?"788msgid "What music formats are supported? !!T"
791msgstr ""789msgstr ""
792790
793#. type: Plain text791#. type: Plain text
@@ -795,10 +793,10 @@
795msgid "The music app supports OGG, FLAG and MP3 formats."793msgid "The music app supports OGG, FLAG and MP3 formats."
796msgstr ""794msgstr ""
797795
798#. !!T796#. type: Title ###
799#: content/pages/apps.md:36797#: content/pages/apps.md:36
800#, no-wrap798#, no-wrap
801msgid "How do I listen to podcasts? !"799msgid "How do I listen to podcasts? !!!T"
802msgstr ""800msgstr ""
803801
804#. type: Plain text802#. type: Plain text
@@ -814,20 +812,20 @@
814msgid "Contacts"812msgid "Contacts"
815msgstr ""813msgstr ""
816814
817#. !!T815#. type: Title ###
818#: content/pages/apps.md:41816#: content/pages/apps.md:41
819#, no-wrap817#, no-wrap
820msgid "How can I sync my Google contacts to my device?"818msgid "How can I sync my Google contacts to my device? !!T"
821msgstr ""819msgstr ""
822820
823#. type: Plain text821#. type: Plain text
824#: content/pages/apps.md:43822#: content/pages/apps.md:43
825msgid ""823msgid ""
826"The first time you open the Contacts app you’ll be asked if you want to sync"824"The first time you open the Contacts app you’ll be asked if you want to sync "
827" contacts with your Google account. If you have answered “no” but change "825"contacts with your Google account. If you have answered “no” but change your "
828"your mind later, you can do so by going to the Today scope, and setting up "826"mind later, you can do so by going to the Today scope, and setting up your "
829"your Google account there. After that you can sync your contacts (and, if "827"Google account there. After that you can sync your contacts (and, if you "
830"you want, calendar events as well)."828"want, calendar events as well)."
831msgstr ""829msgstr ""
832830
833#. type: Title ##831#. type: Title ##
@@ -836,10 +834,10 @@
836msgid "Gallery"834msgid "Gallery"
837msgstr ""835msgstr ""
838836
839#. !!T837#. type: Title ###
840#: content/pages/apps.md:46838#: content/pages/apps.md:46
841#, no-wrap839#, no-wrap
842msgid "How can I share photos?"840msgid "How can I share photos? !!T"
843msgstr ""841msgstr ""
844842
845#. type: Plain text843#. type: Plain text
@@ -851,19 +849,19 @@
851"you wish to share your photo."849"you wish to share your photo."
852msgstr ""850msgstr ""
853851
854#. !!T852#. type: Title ###
855#: content/pages/apps.md:49853#: content/pages/apps.md:49
856#, no-wrap854#, no-wrap
857msgid "How can I share videos?"855msgid "How can I share videos? !!T"
858msgstr ""856msgstr ""
859857
860#. type: Plain text858#. type: Plain text
861#: content/pages/apps.md:51859#: content/pages/apps.md:51
862msgid ""860msgid ""
863"If you've just recorded a video, share it easily by swiping to the left from"861"If you've just recorded a video, share it easily by swiping to the left from "
864" the right edge inside the Camera app. Alternatively, use the Gallery app to"862"the right edge inside the Camera app. Alternatively, use the Gallery app to "
865" find the video. Once loaded, select *Share* from the menu and choose how "863"find the video. Once loaded, select *Share* from the menu and choose how you "
866"you wish to share your video."864"wish to share your video."
867msgstr ""865msgstr ""
868866
869#. type: Title ##867#. type: Title ##
@@ -872,10 +870,10 @@
872msgid "Camera"870msgid "Camera"
873msgstr ""871msgstr ""
874872
875#. !!T873#. type: Title ###
876#: content/pages/apps.md:54874#: content/pages/apps.md:54
877#, no-wrap875#, no-wrap
878msgid "How can I take a picture?"876msgid "How can I take a picture? !!T"
879msgstr ""877msgstr ""
880878
881#. type: Plain text879#. type: Plain text
@@ -885,31 +883,31 @@
885"bottom edge of the phone for additional options. Enjoy taking your picture!"883"bottom edge of the phone for additional options. Enjoy taking your picture!"
886msgstr ""884msgstr ""
887885
888#. !!T886#. type: Title ###
889#: content/pages/apps.md:57887#: content/pages/apps.md:57
890#, no-wrap888#, no-wrap
891msgid "How can I crop / rotate a picture?"889msgid "How can I crop / rotate a picture? !!T"
892msgstr ""890msgstr ""
893891
894#. type: Plain text892#. type: Plain text
895#: content/pages/apps.md:59893#: content/pages/apps.md:59
896msgid ""894msgid ""
897"Use the gallery app to select your picture. Select the *Edit* button next to"895"Use the gallery app to select your picture. Select the *Edit* button next to "
898" the menu. Inside you'll find options to crop and rotate your picture."896"the menu. Inside you'll find options to crop and rotate your picture."
899msgstr ""897msgstr ""
900898
901#. !!T899#. type: Title ###
902#: content/pages/apps.md:60900#: content/pages/apps.md:60
903#, no-wrap901#, no-wrap
904msgid "How can I record video?"902msgid "How can I record video? !!T"
905msgstr ""903msgstr ""
906904
907#. type: Plain text905#. type: Plain text
908#: content/pages/apps.md:62906#: content/pages/apps.md:62
909msgid ""907msgid ""
910"Select the Camera app from the launcher or apps scope. Select the video icon"908"Select the Camera app from the launcher or apps scope. Select the video icon "
911" on the bottom of the screen. Swipe up from the bottom edge of the phone for"909"on the bottom of the screen. Swipe up from the bottom edge of the phone for "
912" additional options. Enjoy taking your video!"910"additional options. Enjoy taking your video!"
913msgstr ""911msgstr ""
914912
915#. type: Title ##913#. type: Title ##
@@ -918,10 +916,10 @@
918msgid "Clock"916msgid "Clock"
919msgstr ""917msgstr ""
920918
921#. !!T919#. type: Title ###
922#: content/pages/apps.md:65920#: content/pages/apps.md:65
923#, no-wrap921#, no-wrap
924msgid "How do I set an alarm?"922msgid "How do I set an alarm? !!T"
925msgstr ""923msgstr ""
926924
927#. type: Plain text925#. type: Plain text
@@ -943,10 +941,10 @@
943msgid "HERE Maps"941msgid "HERE Maps"
944msgstr ""942msgstr ""
945943
946#. !!T944#. type: Title ###
947#: content/pages/apps.md:71945#: content/pages/apps.md:71
948#, no-wrap946#, no-wrap
949msgid "How can I get directions?"947msgid "How can I get directions? !!T"
950msgstr ""948msgstr ""
951949
952#. type: Plain text950#. type: Plain text
@@ -956,10 +954,10 @@
956"*Directions*. Enter your destination and tap the *Get Directions* button."954"*Directions*. Enter your destination and tap the *Get Directions* button."
957msgstr ""955msgstr ""
958956
959#. !!T957#. type: Title ###
960#: content/pages/apps.md:74958#: content/pages/apps.md:74
961#, no-wrap959#, no-wrap
962msgid "Can I navigate offline?"960msgid "Can I navigate offline? !!T"
963msgstr ""961msgstr ""
964962
965#. type: Plain text963#. type: Plain text
@@ -967,10 +965,10 @@
967msgid "Unfortunately navigation requires an active connection."965msgid "Unfortunately navigation requires an active connection."
968msgstr ""966msgstr ""
969967
970#. !!T968#. type: Title ###
971#: content/pages/apps.md:77969#: content/pages/apps.md:77
972#, no-wrap970#, no-wrap
973msgid "Can I view the map offline?"971msgid "Can I view the map offline? !!T"
974msgstr ""972msgstr ""
975973
976#. type: Plain text974#. type: Plain text
@@ -982,7 +980,7 @@
982980
983#. type: Plain text981#. type: Plain text
984#: content/pages/get-in-touch.md:2982#: content/pages/get-in-touch.md:2
985msgid "Get in touch"983msgid "Title: Get in touch"
986msgstr ""984msgstr ""
987985
988#. type: Plain text986#. type: Plain text
@@ -1007,7 +1005,7 @@
10071005
1008#. type: Plain text1006#. type: Plain text
1009#: content/pages/scopes.md:21007#: content/pages/scopes.md:2
1010msgid "Scopes"1008msgid "Title: Scopes"
1011msgstr ""1009msgstr ""
10121010
1013#. type: Plain text1011#. type: Plain text
@@ -1016,25 +1014,25 @@
1016msgid "*Curious about scopes?*\n"1014msgid "*Curious about scopes?*\n"
1017msgstr ""1015msgstr ""
10181016
1019#. !!T1017#. type: Title ###
1020#: content/pages/scopes.md:71018#: content/pages/scopes.md:7
1021#, no-wrap1019#, no-wrap
1022msgid "How do favorites work?"1020msgid "How do favorites work? !!T"
1023msgstr ""1021msgstr ""
10241022
1025#. type: Plain text1023#. type: Plain text
1026#: content/pages/scopes.md:91024#: content/pages/scopes.md:9
1027msgid ""1025msgid ""
1028"Swipe up from the bottom edge of the dash to reveal a scopes manager. "1026"Swipe up from the bottom edge of the dash to reveal a scopes "
1029"Favorite scopes you wish to appear on your dash by selecting them. Selecting"1027"manager. Favorite scopes you wish to appear on your dash by selecting "
1030" them again will remove the favorite. All favorited scopes will appear on "1028"them. Selecting them again will remove the favorite. All favorited scopes "
1031"your dash."1029"will appear on your dash."
1032msgstr ""1030msgstr ""
10331031
1034#. !!T1032#. type: Title ###
1035#: content/pages/scopes.md:101033#: content/pages/scopes.md:10
1036#, no-wrap1034#, no-wrap
1037msgid "How do I add new scopes?"1035msgid "How do I add new scopes? !!T"
1038msgstr ""1036msgstr ""
10391037
1040#. type: Plain text1038#. type: Plain text
@@ -1046,10 +1044,10 @@
1046"store button in the upper right to look for it in the ubuntu store."1044"store button in the upper right to look for it in the ubuntu store."
1047msgstr ""1045msgstr ""
10481046
1049#. !!T1047#. type: Title ###
1050#: content/pages/scopes.md:131048#: content/pages/scopes.md:13
1051#, no-wrap1049#, no-wrap
1052msgid "How do I remove a scope?"1050msgid "How do I remove a scope? !!T"
1053msgstr ""1051msgstr ""
10541052
1055#. type: Plain text1053#. type: Plain text
10561054
=== added directory 'static'
=== renamed directory 'app' => 'static/app'
=== renamed file 'edit-here/index.html' => 'static/index.html'
=== renamed directory 'edit-here/themes' => 'static/themes'
=== renamed directory 'edit-here/themes/phone' => 'static/themes/app'

Subscribers

People subscribed via source and target branches