|
| 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() |
0 commit comments