Skip to content

Commit be96c0c

Browse files
committed
Add support for android-l10n
1 parent a0cc21e commit be96c0c

13 files changed

Lines changed: 261 additions & 30 deletions

File tree

app/classes/Transvision/AnalyseStrings.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,19 @@ public static function differences($tmx_source, $tmx_target, $repo, $ignored_str
4747

4848
$patterns = [
4949
// &foobar;
50-
'dtd' => '/&([A-Za-z0-9\.]+);/',
50+
'dtd' => '/&([A-Za-z0-9\.]+);/',
5151
// { $foo }, { foo }, { -foo }, { -foo[bar] }, { -foo(attr: "value") } Used in FTL files
52-
'ftl' => '/(?<!\{)\{\s*([\$|-]?[A-Za-z0-9_-]+)(?:[\[(]?[A-Za-z0-9_\- :"]+[\])])*\s*\}/u',
52+
'ftl' => '/(?<!\{)\{\s*([\$|-]?[A-Za-z0-9_-]+)(?:[\[(]?[A-Za-z0-9_\- :"]+[\])])*\s*\}/u',
5353
// %@, but also %1$@, %2$@, etc.
54-
'ios' => '/(%(?:[0-9]+\$){0,1}@)/i',
54+
'ios' => '/(%(?:[0-9]+\$){0,1}@)/i',
5555
// {{foobar2}} Used in Loop and PDFViewer
56-
'l10njs' => '/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/u',
56+
'l10njs' => '/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/u',
5757
// %1$S or %S. %1$0.S and %0.S are valid too
58-
'printf' => '/(%(?:[0-9]+\$){0,1}(?:[0-9].){0,1}([sS]))/',
58+
'printf' => '/(%(?:[0-9]+\$){0,1}(?:[0-9].){0,1}([sS]))/',
5959
// $BrandShortName, but not "My%1$SFeeds-%2$S.opml" or "{ $brandShortName }"
60-
'properties' => '/(?<!%[0-9]|\{\s)(\$[A-Za-z0-9\.]+)\b/',
60+
'properties' => '/(?<!%[0-9]|\{\s)(\$[A-Za-z0-9\.]+)\b/',
61+
// %1$s or %s. %d
62+
'xml_android' => '/(%(?:[0-9]+\$){0,1}([sd]))/',
6163
];
6264
$repo_patterns = Project::$repos_info[$repo]['variable_patterns'];
6365

app/classes/Transvision/Project.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ class Project
9797
'pontoon_project' => 'mozillaorg',
9898
'source_type' => 'dotlang',
9999
],
100+
'android_l10n' => [
101+
'git_repository' => 'android-l10n',
102+
'locale_mapping' => [], // To avoid using Bugzilla
103+
'pontoon_project' => 'android-l10n',
104+
'source_type' => 'xml',
105+
'variable_patterns' => ['xml_android'],
106+
],
100107
];
101108

102109
/*
@@ -110,7 +117,8 @@ class Project
110117
],
111118
// Products using Git
112119
'git' => [
113-
'firefox_ios', 'focus_android', 'focus_ios', 'mozilla_org',
120+
'android_l10n', 'firefox_ios', 'focus_android', 'focus_ios',
121+
'mozilla_org',
114122
],
115123
// Products using free text search on Pontoon
116124
'text_search' => [
@@ -279,7 +287,6 @@ public static function isValidRepository($repository)
279287
public static function getLocaleInContext($locale, $context)
280288
{
281289
$locale_mappings = [];
282-
283290
// Bugzilla locales
284291
$locale_mappings['bugzilla'] = [
285292
'es' => 'es-ES',

app/classes/Transvision/VersionControl.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,26 @@ public static function gitPath($locale, $repo, $path)
123123
{
124124
if (isset(Project::$repos_info[$repo]) && isset(Project::$repos_info[$repo]['git_repository'])) {
125125
$repo_data = Project::$repos_info[$repo];
126-
$repo = $repo_data['git_repository'];
126+
$git_repo = $repo_data['git_repository'];
127127
$file_path = self::extractFilePath($path);
128128
if (isset($repo_data['git_subfolder'])) {
129129
$file_path = "{$repo_data['git_subfolder']}/{$file_path}";
130130
}
131+
if ($repo == 'android_l10n') {
132+
// Special case for android-l10n (Android)
133+
$locale_android = $locale == 'en-US'
134+
? ''
135+
: '-' . str_replace('-', '-r', $locale);
136+
$file_path = str_replace('values', "values{$locale_android}", $file_path);
137+
138+
return "https://github.com/mozilla-l10n/{$git_repo}/blob/master/{$file_path}";
139+
}
131140
} else {
132141
$file_path = $path;
142+
$git_repo = $repo;
133143
}
134144

135-
return "https://github.com/mozilla-l10n/{$repo}/blob/master/{$locale}/$file_path";
145+
return "https://github.com/mozilla-l10n/{$git_repo}/blob/master/{$locale}/{$file_path}";
136146
}
137147

138148
/**

app/scripts/bash_variables.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ folders+=( $mozilla_org )
2020
firefox_ios=$local_git/firefox_ios/
2121
focus_ios=$local_git/focus_ios/
2222
focus_android=$local_git/focus_android/
23-
folders+=( $firefox_ios $focus_android $focus_ios )
23+
android_l10n=$local_git/android_l10n/
24+
folders+=( $firefox_ios $focus_android $focus_ios $android_l10n)

app/scripts/glossaire.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,18 @@ function updateOtherProduct() {
281281
nice -20 $install/app/scripts/tmx/$3 $1
282282
}
283283

284+
function updateAndroidl10n() {
285+
if [ "$checkrepo" = true ]
286+
then
287+
cd $android_l10n
288+
echogreen "Update GitHub repository"
289+
git pull
290+
fi
291+
echogreen "Extract strings for android-l10n"
292+
cd $install
293+
nice -20 $install/app/scripts/tmx/tmx_android.py $android_l10n/l10n.toml en-US android_l10n
294+
}
295+
284296
echogreen "Activating virtualenv..."
285297
source $install/python-venv/bin/activate || exit 1
286298

@@ -289,6 +301,7 @@ updateOtherProduct mozilla_org "mozilla.org" tmx_mozillaorg
289301
updateOtherProduct firefox_ios "Firefox for iOS" tmx_xliff
290302
updateOtherProduct focus_ios "Focus for iOS" tmx_xliff
291303
updateOtherProduct focus_android "Focus for Android" tmx_gettext
304+
updateAndroidl10n
292305

293306
# Create a file to get the timestamp of the last string extraction for caching
294307
echogreen "Creating extraction timestamp for cache system"

app/scripts/setup.sh

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,45 +147,48 @@ initGeckoStringsRepo
147147
initOtherSources
148148

149149
# Check out GitHub repos
150-
echogreen "mozilla.org repo being checked out from GitHub"
151150
cd $mozilla_org
152151
if [ ! -d $mozilla_org/.git ]
153152
then
154153
echogreen "Checking out mozilla.org repo"
155154
git clone https://github.com/mozilla-l10n/www.mozilla.org .
156155
fi
157156

158-
echogreen "Firefox for iOS repo being checked out from GitHub"
159157
cd $firefox_ios
160158
if [ ! -d $firefox_ios/.git ]
161159
then
162160
echogreen "Checking out Firefox for iOS repo"
163161
git clone https://github.com/mozilla-l10n/firefoxios-l10n .
164162
fi
165163

166-
echogreen "Focus for iOS repo being checked out from GitHub"
167164
cd $focus_ios
168165
if [ ! -d $focus_ios/.git ]
169166
then
170167
echogreen "Checking out Focus for iOS repo"
171168
git clone https://github.com/mozilla-l10n/focusios-l10n .
172169
fi
173170

174-
echogreen "Focus for Android repo being checked out from GitHub"
175171
cd $focus_android
176172
if [ ! -d $focus_android/.git ]
177173
then
178174
echogreen "Checking out Focus for Android repo"
179175
git clone https://github.com/mozilla-l10n/focus-android-l10n .
180176
fi
181177

178+
cd $android_l10n
179+
if [ ! -d $android_l10n/.git ]
180+
then
181+
echogreen "Checking out Focus for android-l10n repo"
182+
git clone https://github.com/mozilla-l10n/android-l10n .
183+
fi
184+
182185
# Add .htaccess to download folder. Folder should already exists, but check in
183186
# advance to be sure. I overwrite an existing .htaccess if already present.
184187
echogreen "Add .htaccess to download folder"
185188
if [ ! -d $install/web/download ]
186-
then
187-
echogreen "Creating download folder"
188-
mkdir -p $install/web/download
189+
then
190+
echogreen "Creating download folder"
191+
mkdir -p $install/web/download
189192
fi
190193
echo "AddType application/octet-stream .tmx" > $install/web/download/.htaccess
191194

app/scripts/tmx/tmx_android.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import codecs
5+
import json
6+
import logging
7+
import os
8+
import sys
9+
10+
# Python 2/3 compatibility
11+
try:
12+
from ConfigParser import SafeConfigParser
13+
except ImportError:
14+
from configparser import SafeConfigParser
15+
16+
logging.basicConfig()
17+
# Get absolute path of ../../config from the current script location (not the
18+
# current folder)
19+
config_folder = os.path.abspath(os.path.join(
20+
os.path.dirname(__file__), os.pardir, os.pardir, 'config'))
21+
22+
# Read Transvision's configuration file from ../../config/config.ini
23+
# If not available use a default storage folder to store data
24+
config_file = os.path.join(config_folder, 'config.ini')
25+
if not os.path.isfile(config_file):
26+
print('Configuration file /app/config/config.ini is missing. '
27+
'Default settings will be used.')
28+
root_folder = os.path.abspath(
29+
os.path.join(os.path.dirname(__file__), os.pardir))
30+
else:
31+
config_parser = SafeConfigParser()
32+
config_parser.read(config_file)
33+
storage_path = os.path.join(config_parser.get('config', 'root'), 'TMX')
34+
35+
try:
36+
from compare_locales import paths
37+
from compare_locales.parser import getParser
38+
except ImportError as e:
39+
print('FATAL: make sure that dependencies are installed')
40+
print(e)
41+
sys.exit(1)
42+
43+
44+
class StringExtraction():
45+
46+
def __init__(
47+
self, toml_path, storage_path,
48+
reference_locale, repository_name):
49+
'''Initialize object.'''
50+
51+
# Set defaults
52+
self.translations = {}
53+
54+
# Set instance variables
55+
self.toml_path = toml_path
56+
self.storage_path = storage_path
57+
self.reference_locale = reference_locale
58+
self.repository_name = repository_name
59+
60+
def extractStrings(self):
61+
'''Extract strings from all locales.'''
62+
63+
basedir = os.path.dirname(self.toml_path)
64+
project_config = paths.TOMLParser().parse(
65+
self.toml_path, env={'l10n_base': ''})
66+
basedir = os.path.join(basedir, project_config.root)
67+
68+
reference_cache = {}
69+
self.translations[self.reference_locale] = {}
70+
for locale in project_config.all_locales:
71+
files = paths.ProjectFiles(locale, [project_config])
72+
self.translations[locale] = {}
73+
for l10n_file, reference_file, _, _ in files:
74+
if not os.path.exists(l10n_file):
75+
# File not available in localization
76+
continue
77+
78+
if not os.path.exists(reference_file):
79+
# File not available in reference
80+
continue
81+
82+
key_path = os.path.relpath(reference_file, basedir)
83+
try:
84+
p = getParser(reference_file)
85+
except UserWarning:
86+
continue
87+
if key_path not in reference_cache:
88+
p.readFile(reference_file)
89+
reference_cache[key_path] = set(p.parse().keys())
90+
self.translations[self.reference_locale].update(
91+
('{}/{}:{}'.format(
92+
self.repository_name, key_path, entity.key),
93+
entity.raw_val)
94+
for entity in p.parse()
95+
)
96+
97+
p.readFile(l10n_file)
98+
self.translations[locale].update(
99+
('{}/{}:{}'.format(
100+
self.repository_name, key_path, entity.key),
101+
entity.raw_val)
102+
for entity in p.parse()
103+
)
104+
105+
def storeTranslations(self, output_format):
106+
'''
107+
Store translations on file.
108+
If no format is specified, both JSON and PHP formats will
109+
be stored on file.
110+
'''
111+
112+
for locale in self.translations:
113+
translations = self.translations[locale]
114+
storage_file = os.path.join(
115+
self.storage_path, locale,
116+
'cache_{0}_{1}'.format(locale, self.repository_name))
117+
118+
if output_format != 'php':
119+
# Store translations in JSON format
120+
with open('{}.json'.format(storage_file), 'w') as f:
121+
f.write(json.dumps(translations, sort_keys=True))
122+
123+
if output_format != 'json':
124+
# Store translations in PHP format (array)
125+
string_ids = list(translations.keys())
126+
string_ids.sort()
127+
128+
file_name = '{}.php'.format(storage_file)
129+
with codecs.open(file_name, 'w', encoding='utf-8') as f:
130+
f.write('<?php\n$tmx = [\n')
131+
for string_id in string_ids:
132+
translation = self.escape(translations[string_id])
133+
string_id = self.escape(string_id)
134+
line = u"'{0}' => '{1}',\n".format(
135+
string_id, translation)
136+
f.write(line)
137+
f.write('];\n')
138+
139+
def escape(self, translation):
140+
'''
141+
Escape quotes and backslahes in translation. There are two
142+
issues:
143+
* Internal Python escaping: the string "this is a \", has an internal
144+
representation as "this is a \\".
145+
Also, "\\test" is equivalent to r"\test" (raw string).
146+
* We need to print these strings into a file, with the format of a
147+
PHP array delimited by single quotes ('id' => 'translation'). Hence
148+
we need to escape single quotes, but also escape backslashes.
149+
"this is a 'test'" => "this is a \'test\'"
150+
"this is a \'test\'" => "this is a \\\'test\\\'"
151+
'''
152+
153+
# Escape slashes
154+
escaped_translation = translation.replace('\\', '\\\\')
155+
# Escape single quotes
156+
escaped_translation = escaped_translation.replace('\'', '\\\'')
157+
158+
return escaped_translation
159+
160+
161+
def main():
162+
# Read command line input parameters
163+
parser = argparse.ArgumentParser()
164+
parser.add_argument('toml_path', help='Path to root l10n.toml file')
165+
parser.add_argument('reference_code', help='Reference language code')
166+
parser.add_argument('repository_name', help='Repository name')
167+
parser.add_argument(
168+
'--output', nargs='?', type=str, choices=['json', 'php'],
169+
help='Store only one type of output.', default='')
170+
args = parser.parse_args()
171+
172+
extracted_strings = StringExtraction(
173+
args.toml_path, storage_path,
174+
args.reference_code, args.repository_name)
175+
extracted_strings.extractStrings()
176+
extracted_strings.storeTranslations(args.output)
177+
178+
179+
if __name__ == '__main__':
180+
main()

app/scripts/tmx/tmx_products.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/python
1+
#!/usr/bin/env python
22

33
import argparse
44
import codecs
@@ -109,9 +109,6 @@ def getRelativePath(self, file_name):
109109
if self.storage_prefix != '':
110110
relative_path = '{0}/{1}'.format(self.storage_prefix,
111111
relative_path)
112-
# Hack to work around Transvision symlink mess
113-
relative_path = relative_path.replace(
114-
'locales/en-US/en-US/', '')
115112

116113
return relative_path
117114

app/views/accesskeys.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060
"&search_type=entities&recherche={$ak_label}" .
6161
'&entire_string=entire_string';
6262

63-
$path_ak = VersionControl::hgPath($locale, $repo, $ak_string);
64-
$path_label = VersionControl::hgPath($locale, $repo, $ak_label);
63+
$path_ak = VersionControl::getPath($locale, $repo, $ak_string);
64+
$path_label = VersionControl::getPath($locale, $repo, $ak_label);
6565

6666
$edit_link_ak = $toolUsedByTargetLocale != ''
6767
? ShowResults::getEditLink($toolUsedByTargetLocale, $repo, $ak_string, $accesskey_txt, $locale)

app/views/check_variables.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
$target_string, $repo, $string_id_link
4545
);
4646

47-
$path_source_locale = VersionControl::hgPath($source_locale, $repo, $string_id);
48-
$path_target_locale = VersionControl::hgPath($locale, $repo, $string_id);
47+
$path_source_locale = VersionControl::getPath($source_locale, $repo, $string_id);
48+
$path_target_locale = VersionControl::getPath($locale, $repo, $string_id);
4949
$edit_link = $toolUsedByTargetLocale != ''
5050
? ShowResults::getEditLink($toolUsedByTargetLocale, $repo, $string_id, $target_string, $locale)
5151
: '';

0 commit comments

Comments
 (0)