Skip to content

Commit

Permalink
Add support for android-l10n
Browse files Browse the repository at this point in the history
  • Loading branch information
flodolo committed May 22, 2019
1 parent a0cc21e commit be96c0c
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 30 deletions.
14 changes: 8 additions & 6 deletions app/classes/Transvision/AnalyseStrings.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ public static function differences($tmx_source, $tmx_target, $repo, $ignored_str

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

Expand Down
11 changes: 9 additions & 2 deletions app/classes/Transvision/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ class Project
'pontoon_project' => 'mozillaorg',
'source_type' => 'dotlang',
],
'android_l10n' => [
'git_repository' => 'android-l10n',
'locale_mapping' => [], // To avoid using Bugzilla
'pontoon_project' => 'android-l10n',
'source_type' => 'xml',
'variable_patterns' => ['xml_android'],
],
];

/*
Expand All @@ -110,7 +117,8 @@ class Project
],
// Products using Git
'git' => [
'firefox_ios', 'focus_android', 'focus_ios', 'mozilla_org',
'android_l10n', 'firefox_ios', 'focus_android', 'focus_ios',
'mozilla_org',
],
// Products using free text search on Pontoon
'text_search' => [
Expand Down Expand Up @@ -279,7 +287,6 @@ public static function isValidRepository($repository)
public static function getLocaleInContext($locale, $context)
{
$locale_mappings = [];

// Bugzilla locales
$locale_mappings['bugzilla'] = [
'es' => 'es-ES',
Expand Down
14 changes: 12 additions & 2 deletions app/classes/Transvision/VersionControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,26 @@ public static function gitPath($locale, $repo, $path)
{
if (isset(Project::$repos_info[$repo]) && isset(Project::$repos_info[$repo]['git_repository'])) {
$repo_data = Project::$repos_info[$repo];
$repo = $repo_data['git_repository'];
$git_repo = $repo_data['git_repository'];
$file_path = self::extractFilePath($path);
if (isset($repo_data['git_subfolder'])) {
$file_path = "{$repo_data['git_subfolder']}/{$file_path}";
}
if ($repo == 'android_l10n') {
// Special case for android-l10n (Android)
$locale_android = $locale == 'en-US'
? ''
: '-' . str_replace('-', '-r', $locale);
$file_path = str_replace('values', "values{$locale_android}", $file_path);

return "https://github.com/mozilla-l10n/{$git_repo}/blob/master/{$file_path}";
}
} else {
$file_path = $path;
$git_repo = $repo;
}

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

/**
Expand Down
3 changes: 2 additions & 1 deletion app/scripts/bash_variables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ folders+=( $mozilla_org )
firefox_ios=$local_git/firefox_ios/
focus_ios=$local_git/focus_ios/
focus_android=$local_git/focus_android/
folders+=( $firefox_ios $focus_android $focus_ios )
android_l10n=$local_git/android_l10n/
folders+=( $firefox_ios $focus_android $focus_ios $android_l10n)
13 changes: 13 additions & 0 deletions app/scripts/glossaire.sh
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ function updateOtherProduct() {
nice -20 $install/app/scripts/tmx/$3 $1
}

function updateAndroidl10n() {
if [ "$checkrepo" = true ]
then
cd $android_l10n
echogreen "Update GitHub repository"
git pull
fi
echogreen "Extract strings for android-l10n"
cd $install
nice -20 $install/app/scripts/tmx/tmx_android.py $android_l10n/l10n.toml en-US android_l10n
}

echogreen "Activating virtualenv..."
source $install/python-venv/bin/activate || exit 1

Expand All @@ -289,6 +301,7 @@ updateOtherProduct mozilla_org "mozilla.org" tmx_mozillaorg
updateOtherProduct firefox_ios "Firefox for iOS" tmx_xliff
updateOtherProduct focus_ios "Focus for iOS" tmx_xliff
updateOtherProduct focus_android "Focus for Android" tmx_gettext
updateAndroidl10n

# Create a file to get the timestamp of the last string extraction for caching
echogreen "Creating extraction timestamp for cache system"
Expand Down
17 changes: 10 additions & 7 deletions app/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -147,45 +147,48 @@ initGeckoStringsRepo
initOtherSources

# Check out GitHub repos
echogreen "mozilla.org repo being checked out from GitHub"
cd $mozilla_org
if [ ! -d $mozilla_org/.git ]
then
echogreen "Checking out mozilla.org repo"
git clone https://github.com/mozilla-l10n/www.mozilla.org .
fi

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

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

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

cd $android_l10n
if [ ! -d $android_l10n/.git ]
then
echogreen "Checking out Focus for android-l10n repo"
git clone https://github.com/mozilla-l10n/android-l10n .
fi

# Add .htaccess to download folder. Folder should already exists, but check in
# advance to be sure. I overwrite an existing .htaccess if already present.
echogreen "Add .htaccess to download folder"
if [ ! -d $install/web/download ]
then
echogreen "Creating download folder"
mkdir -p $install/web/download
then
echogreen "Creating download folder"
mkdir -p $install/web/download
fi
echo "AddType application/octet-stream .tmx" > $install/web/download/.htaccess

Expand Down
180 changes: 180 additions & 0 deletions app/scripts/tmx/tmx_android.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env python

import argparse
import codecs
import json
import logging
import os
import sys

# Python 2/3 compatibility
try:
from ConfigParser import SafeConfigParser
except ImportError:
from configparser import SafeConfigParser

logging.basicConfig()
# Get absolute path of ../../config from the current script location (not the
# current folder)
config_folder = os.path.abspath(os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, 'config'))

# Read Transvision's configuration file from ../../config/config.ini
# If not available use a default storage folder to store data
config_file = os.path.join(config_folder, 'config.ini')
if not os.path.isfile(config_file):
print('Configuration file /app/config/config.ini is missing. '
'Default settings will be used.')
root_folder = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir))
else:
config_parser = SafeConfigParser()
config_parser.read(config_file)
storage_path = os.path.join(config_parser.get('config', 'root'), 'TMX')

try:
from compare_locales import paths
from compare_locales.parser import getParser
except ImportError as e:
print('FATAL: make sure that dependencies are installed')
print(e)
sys.exit(1)


class StringExtraction():

def __init__(
self, toml_path, storage_path,
reference_locale, repository_name):
'''Initialize object.'''

# Set defaults
self.translations = {}

# Set instance variables
self.toml_path = toml_path
self.storage_path = storage_path
self.reference_locale = reference_locale
self.repository_name = repository_name

def extractStrings(self):
'''Extract strings from all locales.'''

basedir = os.path.dirname(self.toml_path)
project_config = paths.TOMLParser().parse(
self.toml_path, env={'l10n_base': ''})
basedir = os.path.join(basedir, project_config.root)

reference_cache = {}
self.translations[self.reference_locale] = {}
for locale in project_config.all_locales:
files = paths.ProjectFiles(locale, [project_config])
self.translations[locale] = {}
for l10n_file, reference_file, _, _ in files:
if not os.path.exists(l10n_file):
# File not available in localization
continue

if not os.path.exists(reference_file):
# File not available in reference
continue

key_path = os.path.relpath(reference_file, basedir)
try:
p = getParser(reference_file)
except UserWarning:
continue
if key_path not in reference_cache:
p.readFile(reference_file)
reference_cache[key_path] = set(p.parse().keys())
self.translations[self.reference_locale].update(
('{}/{}:{}'.format(
self.repository_name, key_path, entity.key),
entity.raw_val)
for entity in p.parse()
)

p.readFile(l10n_file)
self.translations[locale].update(
('{}/{}:{}'.format(
self.repository_name, key_path, entity.key),
entity.raw_val)
for entity in p.parse()
)

def storeTranslations(self, output_format):
'''
Store translations on file.
If no format is specified, both JSON and PHP formats will
be stored on file.
'''

for locale in self.translations:
translations = self.translations[locale]
storage_file = os.path.join(
self.storage_path, locale,
'cache_{0}_{1}'.format(locale, self.repository_name))

if output_format != 'php':
# Store translations in JSON format
with open('{}.json'.format(storage_file), 'w') as f:
f.write(json.dumps(translations, sort_keys=True))

if output_format != 'json':
# Store translations in PHP format (array)
string_ids = list(translations.keys())
string_ids.sort()

file_name = '{}.php'.format(storage_file)
with codecs.open(file_name, 'w', encoding='utf-8') as f:
f.write('<?php\n$tmx = [\n')
for string_id in string_ids:
translation = self.escape(translations[string_id])
string_id = self.escape(string_id)
line = u"'{0}' => '{1}',\n".format(
string_id, translation)
f.write(line)
f.write('];\n')

def escape(self, translation):
'''
Escape quotes and backslahes in translation. There are two
issues:
* Internal Python escaping: the string "this is a \", has an internal
representation as "this is a \\".
Also, "\\test" is equivalent to r"\test" (raw string).
* We need to print these strings into a file, with the format of a
PHP array delimited by single quotes ('id' => 'translation'). Hence
we need to escape single quotes, but also escape backslashes.
"this is a 'test'" => "this is a \'test\'"
"this is a \'test\'" => "this is a \\\'test\\\'"
'''

# Escape slashes
escaped_translation = translation.replace('\\', '\\\\')
# Escape single quotes
escaped_translation = escaped_translation.replace('\'', '\\\'')

return escaped_translation


def main():
# Read command line input parameters
parser = argparse.ArgumentParser()
parser.add_argument('toml_path', help='Path to root l10n.toml file')
parser.add_argument('reference_code', help='Reference language code')
parser.add_argument('repository_name', help='Repository name')
parser.add_argument(
'--output', nargs='?', type=str, choices=['json', 'php'],
help='Store only one type of output.', default='')
args = parser.parse_args()

extracted_strings = StringExtraction(
args.toml_path, storage_path,
args.reference_code, args.repository_name)
extracted_strings.extractStrings()
extracted_strings.storeTranslations(args.output)


if __name__ == '__main__':
main()
5 changes: 1 addition & 4 deletions app/scripts/tmx/tmx_products.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python

import argparse
import codecs
Expand Down Expand Up @@ -109,9 +109,6 @@ def getRelativePath(self, file_name):
if self.storage_prefix != '':
relative_path = '{0}/{1}'.format(self.storage_prefix,
relative_path)
# Hack to work around Transvision symlink mess
relative_path = relative_path.replace(
'locales/en-US/en-US/', '')

return relative_path

Expand Down
4 changes: 2 additions & 2 deletions app/views/accesskeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
"&search_type=entities&recherche={$ak_label}" .
'&entire_string=entire_string';

$path_ak = VersionControl::hgPath($locale, $repo, $ak_string);
$path_label = VersionControl::hgPath($locale, $repo, $ak_label);
$path_ak = VersionControl::getPath($locale, $repo, $ak_string);
$path_label = VersionControl::getPath($locale, $repo, $ak_label);

$edit_link_ak = $toolUsedByTargetLocale != ''
? ShowResults::getEditLink($toolUsedByTargetLocale, $repo, $ak_string, $accesskey_txt, $locale)
Expand Down
4 changes: 2 additions & 2 deletions app/views/check_variables.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
$target_string, $repo, $string_id_link
);

$path_source_locale = VersionControl::hgPath($source_locale, $repo, $string_id);
$path_target_locale = VersionControl::hgPath($locale, $repo, $string_id);
$path_source_locale = VersionControl::getPath($source_locale, $repo, $string_id);
$path_target_locale = VersionControl::getPath($locale, $repo, $string_id);
$edit_link = $toolUsedByTargetLocale != ''
? ShowResults::getEditLink($toolUsedByTargetLocale, $repo, $string_id, $target_string, $locale)
: '';
Expand Down
Loading

0 comments on commit be96c0c

Please sign in to comment.