scratch-blocks 2.1.19 → 2.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/AGENTS.md +20 -0
  2. package/CLAUDE.md +1 -0
  3. package/dist/main.mjs +1 -1
  4. package/dist/types/src/index.d.ts +1 -1
  5. package/dist/types/src/index.d.ts.map +1 -1
  6. package/dist/types/src/renderer/renderer.d.ts.map +1 -1
  7. package/dist/types/src/scratch_msgs.d.ts +32 -0
  8. package/dist/types/src/scratch_msgs.d.ts.map +1 -0
  9. package/eslint.config.mjs +0 -1
  10. package/msg/json/en.json +6 -0
  11. package/package.json +17 -14
  12. package/scripts/validate-en-json.mjs +27 -0
  13. package/src/fields/scratch_field_variable.ts +2 -2
  14. package/src/index.ts +2 -2
  15. package/src/renderer/constants.ts +1 -1
  16. package/src/renderer/drawer.ts +1 -1
  17. package/src/renderer/renderer.ts +2 -1
  18. package/src/scratch_c_block_wrap.ts +3 -2
  19. package/src/scratch_comment_bubble.ts +1 -1
  20. package/src/scratch_connection_checker.ts +2 -1
  21. package/src/scratch_msgs.ts +70 -0
  22. package/tsconfig.json +1 -0
  23. package/types/scratch-l10n.d.ts +15 -0
  24. package/dist/types/msg/scratch_msgs.d.ts +0 -9
  25. package/dist/types/msg/scratch_msgs.d.ts.map +0 -1
  26. package/i18n/common.py +0 -234
  27. package/i18n/create_messages.py +0 -162
  28. package/i18n/create_scratch_msgs.js +0 -68
  29. package/i18n/dedup_json.py +0 -73
  30. package/i18n/js_to_json.js +0 -54
  31. package/i18n/js_to_json.py +0 -136
  32. package/i18n/json_to_js.js +0 -52
  33. package/i18n/json_to_js.py +0 -185
  34. package/i18n/sync_tx_translations.js +0 -110
  35. package/i18n/test_scratch_msgs.js +0 -90
  36. package/i18n/tests.py +0 -47
  37. package/i18n/xliff_to_json.py +0 -232
  38. package/msg/js/en.js +0 -293
  39. package/msg/messages.js +0 -356
  40. package/msg/scratch_msgs.js +0 -23058
@@ -1,185 +0,0 @@
1
- #!/usr/bin/python
2
-
3
- # Converts .json files into .js files for use within Blockly apps.
4
- #
5
- # Copyright 2013 Google Inc.
6
- # https://developers.google.com/blockly/
7
- #
8
- # Licensed under the Apache License, Version 2.0 (the "License");
9
- # you may not use this file except in compliance with the License.
10
- # You may obtain a copy of the License at
11
- #
12
- # http://www.apache.org/licenses/LICENSE-2.0
13
- #
14
- # Unless required by applicable law or agreed to in writing, software
15
- # distributed under the License is distributed on an "AS IS" BASIS,
16
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
- # See the License for the specific language governing permissions and
18
- # limitations under the License.
19
-
20
- import argparse
21
- import codecs # for codecs.open(..., 'utf-8')
22
- import glob
23
- import json # for json.load()
24
- import os # for os.path()
25
- import subprocess # for subprocess.check_call()
26
- from common import InputError
27
- from common import read_json_file
28
-
29
-
30
- # Store parsed command-line arguments in global variable.
31
- args = None
32
-
33
-
34
- def _create_xlf(target_lang):
35
- """Creates a <target_lang>.xlf file for Soy.
36
-
37
- Args:
38
- target_lang: The ISO 639 language code for the target language.
39
- This is used in the name of the file and in the metadata.
40
-
41
- Returns:
42
- A pointer to a file to which the metadata has been written.
43
-
44
- Raises:
45
- IOError: An error occurred while opening or writing the file.
46
- """
47
- filename = os.path.join(os.curdir, args.output_dir, target_lang + '.xlf')
48
- out_file = codecs.open(filename, 'w', 'utf-8')
49
- out_file.write("""<?xml version="1.0" encoding="UTF-8"?>
50
- <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
51
- <file original="SoyMsgBundle"
52
- datatype="x-soy-msg-bundle"
53
- xml:space="preserve"
54
- source-language="{0}"
55
- target-language="{1}">
56
- <body>""".format(args.source_lang, target_lang))
57
- return out_file
58
-
59
-
60
- def _close_xlf(xlf_file):
61
- """Closes a <target_lang>.xlf file created with create_xlf().
62
-
63
- This includes writing the terminating XML.
64
-
65
- Args:
66
- xlf_file: A pointer to a file created by _create_xlf().
67
-
68
- Raises:
69
- IOError: An error occurred while writing to or closing the file.
70
- """
71
- xlf_file.write("""
72
- </body>
73
- </file>
74
- </xliff>
75
- """)
76
- xlf_file.close()
77
-
78
-
79
- def _process_file(path_to_json, target_lang, key_dict):
80
- """Creates an .xlf file corresponding to the specified .json input file.
81
-
82
- The name of the input file must be target_lang followed by '.json'.
83
- The name of the output file will be target_lang followed by '.js'.
84
-
85
- Args:
86
- path_to_json: Path to the directory of xx.json files.
87
- target_lang: A IETF language code (RFC 4646), such as 'es' or 'pt-br'.
88
- key_dict: Dictionary mapping Blockly keys (e.g., Maze.turnLeft) to
89
- Closure keys (hash numbers).
90
-
91
- Raises:
92
- IOError: An I/O error occurred with an input or output file.
93
- InputError: Input JSON could not be parsed.
94
- KeyError: Key found in input file but not in key file.
95
- """
96
- keyfile = os.path.join(path_to_json, target_lang + '.json')
97
- j = read_json_file(keyfile)
98
- out_file = _create_xlf(target_lang)
99
- for key in j:
100
- if key != '@metadata':
101
- try:
102
- identifier = key_dict[key]
103
- except KeyError as e:
104
- print('Key "%s" is in %s but not in %s' %
105
- (key, keyfile, args.key_file))
106
- raise e
107
- target = j.get(key)
108
- out_file.write(u"""
109
- <trans-unit id="{0}" datatype="html">
110
- <target>{1}</target>
111
- </trans-unit>""".format(identifier, target))
112
- _close_xlf(out_file)
113
-
114
-
115
- def main():
116
- """Parses arguments and iterates over files."""
117
-
118
- # Set up argument parser.
119
- parser = argparse.ArgumentParser(description='Convert JSON files to JS.')
120
- parser.add_argument('--source_lang', default='en',
121
- help='ISO 639-1 source language code')
122
- parser.add_argument('--output_dir', default='generated',
123
- help='relative directory for output files')
124
- parser.add_argument('--key_file', default='json' + os.path.sep + 'keys.json',
125
- help='relative path to input keys file')
126
- parser.add_argument('--template', default='template.soy')
127
- parser.add_argument('--path_to_jar',
128
- default='..' + os.path.sep + 'apps' + os.path.sep
129
- + '_soy',
130
- help='relative path from working directory to '
131
- 'SoyToJsSrcCompiler.jar')
132
- parser.add_argument('files', nargs='+', help='input files')
133
-
134
- # Initialize global variables.
135
- global args
136
- args = parser.parse_args()
137
-
138
- # Make sure output_dir ends with slash.
139
- if (not args.output_dir.endswith(os.path.sep)):
140
- args.output_dir += os.path.sep
141
-
142
- # Read in keys.json, mapping descriptions (e.g., Maze.turnLeft) to
143
- # Closure keys (long hash numbers).
144
- key_file = open(args.key_file)
145
- key_dict = json.load(key_file)
146
- key_file.close()
147
-
148
- # Process each input file.
149
- print('Creating .xlf files...')
150
- processed_langs = []
151
- if len(args.files) == 1:
152
- # Windows does not expand globs automatically.
153
- args.files = glob.glob(args.files[0])
154
- for arg_file in args.files:
155
- (path_to_json, filename) = os.path.split(arg_file)
156
- if not filename.endswith('.json'):
157
- raise InputError(filename, 'filenames must end with ".json"')
158
- target_lang = filename[:filename.index('.')]
159
- if not target_lang in ('qqq', 'keys'):
160
- processed_langs.append(target_lang)
161
- _process_file(path_to_json, target_lang, key_dict)
162
-
163
- # Output command line for Closure compiler.
164
- if processed_langs:
165
- print('Creating .js files...')
166
- processed_lang_list = ','.join(processed_langs)
167
- subprocess.check_call([
168
- 'java',
169
- '-jar', os.path.join(args.path_to_jar, 'SoyToJsSrcCompiler.jar'),
170
- '--locales', processed_lang_list,
171
- '--messageFilePathFormat', args.output_dir + '{LOCALE}.xlf',
172
- '--outputPathFormat', args.output_dir + '{LOCALE}.js',
173
- '--srcs', args.template])
174
- if len(processed_langs) == 1:
175
- print('Created ' + processed_lang_list + '.js in ' + args.output_dir)
176
- else:
177
- print('Created {' + processed_lang_list + '}.js in ' + args.output_dir)
178
-
179
- for lang in processed_langs:
180
- os.remove(args.output_dir + lang + '.xlf')
181
- print('Removed .xlf files.')
182
-
183
-
184
- if __name__ == '__main__':
185
- main()
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * @fileoverview
5
- * Script to pull translations for blocks from transifex and generate the scratch_msgs file.
6
- * Expects that the project and resource have already been defined in Transifex, and that
7
- * the person running the script has the the TX_TOKEN environment variable set to an api
8
- * token that has developer access.
9
- */
10
-
11
- const usage = `
12
- Pull supported language translations from Transifex. Usage:
13
- node sync_tx_translations.js
14
- NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token. See
15
- the Localization page on the GUI wiki for information about setting up Transifex.
16
- `
17
- // Fail immediately if the TX_TOKEN is not defined
18
- if (!process.env.TX_TOKEN || process.argv.length !== 2) {
19
- process.stdout.write(usage)
20
- process.exit(1)
21
- }
22
-
23
- const fs = require('fs')
24
- const path = require('path')
25
- const assert = require('assert')
26
- const locales = require('scratch-l10n').default
27
- const { txPull } = require('scratch-l10n/lib/transifex.js')
28
-
29
- // Globals
30
- const PATH_OUTPUT = path.resolve(__dirname, '../msg')
31
- const PROJECT = 'scratch-editor'
32
- const RESOURCE = 'blocks'
33
- const MODE = 'reviewed'
34
-
35
- let en = fs.readFileSync(path.resolve(__dirname, '../msg/json/en.json'))
36
- en = JSON.parse(en)
37
- const enKeys = Object.keys(en).sort().toString()
38
-
39
- // Check that translation is valid:
40
- // entry: array [key, translation] corresponding to a single string from <locale>.json
41
- // - messages with placeholders have the same number of placeholders
42
- // - messages must not have newlines embedded
43
- const validateEntry = function (entry) {
44
- const re = /(%\d)/g
45
- const [key, translation] = entry
46
- const enMatch = en[key].match(re)
47
- const tMatch = translation.match(re)
48
- const enCount = enMatch ? enMatch.length : 0
49
- const tCount = tMatch ? tMatch.length : 0
50
- assert.strictEqual(tCount, enCount, `${key}:${en[key]} - "${translation}" placeholder mismatch`)
51
- if (enCount > 0) {
52
- assert.notStrictEqual(tMatch, null, `${key} is missing a placeholder: ${translation}`)
53
- assert.strictEqual(
54
- tMatch.sort().toString(),
55
- enMatch.sort().toString(),
56
- `${key} is missing or has duplicate placeholders: ${translation}`,
57
- )
58
- }
59
- assert.strictEqual(translation.match(/[\n]/), null, `${key} contains a newline character ${translation}`)
60
- }
61
-
62
- const validate = function (json, name) {
63
- assert.strictEqual(Object.keys(json).sort().toString(), enKeys, `${name}: Locale json keys do not match en.json`)
64
- Object.entries(json).forEach(validateEntry)
65
- }
66
-
67
- let file = `// This file was automatically generated. Do not modify.
68
-
69
- 'use strict';
70
-
71
- goog.provide('Blockly.ScratchMsgs.allLocales');
72
-
73
- goog.require('Blockly.ScratchMsgs');
74
-
75
- `
76
-
77
- let localeMap = {
78
- 'aa-dj': 'aa_DJ',
79
- 'es-419': 'es_419',
80
- 'pt-br': 'pt_BR',
81
- 'zh-cn': 'zh_CN',
82
- 'zh-tw': 'zh_TW',
83
- }
84
-
85
- const getLocaleData = async function (locale) {
86
- let txLocale = localeMap[locale] || locale
87
- const data = await txPull(PROJECT, RESOURCE, txLocale, MODE)
88
- return {
89
- locale: locale,
90
- translations: data,
91
- }
92
- }
93
-
94
- Promise.all(Object.keys(locales).map(getLocaleData))
95
- .then(function (values) {
96
- values.forEach(function (translation) {
97
- validate(translation.translations, translation.locale)
98
- file += '\n'
99
- file += `Blockly.ScratchMsgs.locales["${translation.locale}"] =\n`
100
- file += JSON.stringify(translation.translations, null, 4)
101
- file += ';\n'
102
- })
103
- file += '// End of combined translations\n'
104
- // write combined file
105
- fs.writeFileSync(`${PATH_OUTPUT}/scratch_msgs.js`, file)
106
- })
107
- .catch((err) => {
108
- console.error(err)
109
- process.exit(1)
110
- })
@@ -1,90 +0,0 @@
1
- const es = require('event-stream')
2
- const fs = require('fs')
3
- const path = require('path')
4
- const assert = require('assert')
5
-
6
- // current locale and keys for the locale
7
- let locale = ''
8
- let keys = []
9
-
10
- // current English keys
11
- let en = fs.readFileSync(path.resolve(__dirname, '../msg/json/en.json'))
12
- en = JSON.parse(en)
13
- const enKeys = Object.keys(en)
14
-
15
- // File paths
16
- const PATH_INPUT = path.resolve(__dirname, '../msg/scratch_msgs.js')
17
-
18
- // Match lines of the scratch_msgs file
19
- // Blockly.ScratchMsgs.locales indicates the start of a new locale
20
- // ": " marks a "key": "value" pair
21
- // Also match the end of the generated file so the last set of keys can be checked
22
- const match = function (str) {
23
- if (str.indexOf('Blockly.ScratchMsgs.locales') !== 0) return true
24
- if (str.indexOf('": "') !== 0) return true
25
- if (str.indexOf('End of combined translations') !== 0) return true
26
- return false
27
- }
28
-
29
- // Extract key and value from message definition, or locale when it changes
30
- const extract = function (str) {
31
- let m = str.match(/locales\["(.+)"\]/)
32
- if (m) {
33
- // locale changed
34
- return m[1]
35
- }
36
- m = str.match(/"(.*)": "(.*)",?$/)
37
- if (m) {
38
- return {
39
- key: m[1],
40
- value: m[2],
41
- }
42
- }
43
- // return a string for the end of the file so that validate will check the last set of keys
44
- m = str.match(/^\/\/ End of combined translations$/)
45
- if (m) return 'last'
46
- return null
47
- }
48
-
49
- const validateKeys = function () {
50
- // ignore empty keys first time through
51
- if (keys.length === 0) return
52
- assert.strictEqual(keys.length, Object.keys(en).length, `scratch_msgs-${locale}: number of keys doesn't match`)
53
- keys.map((item) => assert(enKeys.includes(item), `scratch_msgs-${locale}: has key ${item} not in en`))
54
- enKeys.map((item) => assert(keys.includes(item), `scratch_msgs-${locale}: is missing key ${item}`))
55
- }
56
-
57
- // Stream input and push each match to the storage object
58
- const stream = fs.createReadStream(PATH_INPUT)
59
- stream
60
- .pipe(es.split('\n'))
61
- .pipe(
62
- es.mapSync(function (str) {
63
- if (!match(str)) return
64
- const result = extract(str)
65
- if (!result) return
66
- if (typeof result === 'string') {
67
- // locale changed or end of file, validate the current collection of keys
68
- try {
69
- validateKeys()
70
- } catch (err) {
71
- console.error('Key validation FAILED: %O', err)
72
- process.exit(1)
73
- }
74
- // change locale, and reset keys array
75
- locale = result
76
- keys = []
77
- } else {
78
- keys.push(result.key)
79
- }
80
- }),
81
- )
82
- .pipe(
83
- es.wait(function (err) {
84
- if (err) {
85
- console.error(err)
86
- process.exit(1)
87
- }
88
- process.exit(0)
89
- }),
90
- )
package/i18n/tests.py DELETED
@@ -1,47 +0,0 @@
1
- #!/usr/bin/python
2
- # -*- coding: utf-8 -*-
3
-
4
- # Tests of i18n scripts.
5
- #
6
- # Copyright 2013 Google Inc.
7
- # https://developers.google.com/blockly/
8
- #
9
- # Licensed under the Apache License, Version 2.0 (the "License");
10
- # you may not use this file except in compliance with the License.
11
- # You may obtain a copy of the License at
12
- #
13
- # http://www.apache.org/licenses/LICENSE-2.0
14
- #
15
- # Unless required by applicable law or agreed to in writing, software
16
- # distributed under the License is distributed on an "AS IS" BASIS,
17
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- # See the License for the specific language governing permissions and
19
- # limitations under the License.
20
-
21
- import common
22
- import re
23
- import unittest
24
-
25
- class TestSequenceFunctions(unittest.TestCase):
26
- def test_insert_breaks(self):
27
- spaces = re.compile(r'\s+|\\n')
28
- def contains_all_chars(orig, result):
29
- return re.sub(spaces, '', orig) == re.sub(spaces, '', result)
30
-
31
- sentences = [u'Quay Pegman qua bên trái hoặc bên phải 90 độ.',
32
- u'Foo bar baz this is english that is okay bye.',
33
- u'If there is a path in the specified direction, \nthen ' +
34
- u'do some actions.',
35
- u'If there is a path in the specified direction, then do ' +
36
- u'the first block of actions. Otherwise, do the second ' +
37
- u'block of actions.']
38
- for sentence in sentences:
39
- output = common.insert_breaks(sentence, 30, 50)
40
- self.assertTrue(contains_all_chars(sentence, output),
41
- u'Mismatch between:\n{0}\n{1}'.format(
42
- re.sub(spaces, '', sentence),
43
- re.sub(spaces, '', output)))
44
-
45
-
46
- if __name__ == '__main__':
47
- unittest.main()
@@ -1,232 +0,0 @@
1
- #!/usr/bin/python
2
-
3
- # Converts .xlf files into .json files for use at http://translatewiki.net.
4
- #
5
- # Copyright 2013 Google Inc.
6
- # https://developers.google.com/blockly/
7
- #
8
- # Licensed under the Apache License, Version 2.0 (the "License");
9
- # you may not use this file except in compliance with the License.
10
- # You may obtain a copy of the License at
11
- #
12
- # http://www.apache.org/licenses/LICENSE-2.0
13
- #
14
- # Unless required by applicable law or agreed to in writing, software
15
- # distributed under the License is distributed on an "AS IS" BASIS,
16
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
- # See the License for the specific language governing permissions and
18
- # limitations under the License.
19
-
20
- import argparse
21
- import os
22
- import re
23
- import subprocess
24
- import sys
25
- from xml.dom import minidom
26
- from common import InputError
27
- from common import write_files
28
-
29
- # Global variables
30
- args = None # Parsed command-line arguments.
31
-
32
-
33
- def _parse_trans_unit(trans_unit):
34
- """Converts a trans-unit XML node into a more convenient dictionary format.
35
-
36
- Args:
37
- trans_unit: An XML representation of a .xlf translation unit.
38
-
39
- Returns:
40
- A dictionary with useful information about the translation unit.
41
- The returned dictionary is guaranteed to have an entry for 'key' and
42
- may have entries for 'source', 'target', 'description', and 'meaning'
43
- if present in the argument.
44
-
45
- Raises:
46
- InputError: A required field was not present.
47
- """
48
-
49
- def get_value(tag_name):
50
- elts = trans_unit.getElementsByTagName(tag_name)
51
- if not elts:
52
- return None
53
- elif len(elts) == 1:
54
- return ''.join([child.toxml() for child in elts[0].childNodes])
55
- else:
56
- raise InputError('', 'Unable to extract ' + tag_name)
57
-
58
- result = {}
59
- key = trans_unit.getAttribute('id')
60
- if not key:
61
- raise InputError('', 'id attribute not found')
62
- result['key'] = key
63
-
64
- # Get source and target, if present.
65
- try:
66
- result['source'] = get_value('source')
67
- result['target'] = get_value('target')
68
- except InputError as e:
69
- raise InputError(key, e.msg)
70
-
71
- # Get notes, using the from value as key and the data as value.
72
- notes = trans_unit.getElementsByTagName('note')
73
- for note in notes:
74
- from_value = note.getAttribute('from')
75
- if from_value and len(note.childNodes) == 1:
76
- result[from_value] = note.childNodes[0].data
77
- else:
78
- raise InputError(key, 'Unable to extract ' + from_value)
79
-
80
- return result
81
-
82
-
83
- def _process_file(filename):
84
- """Builds list of translation units from input file.
85
-
86
- Each translation unit in the input file includes:
87
- - an id (opaquely generated by Soy)
88
- - the Blockly name for the message
89
- - the text in the source language (generally English)
90
- - a description for the translator
91
-
92
- The Soy and Blockly ids are joined with a hyphen and serve as the
93
- keys in both output files. The value is the corresponding text (in the
94
- <lang>.json file) or the description (in the qqq.json file).
95
-
96
- Args:
97
- filename: The name of an .xlf file produced by Closure.
98
-
99
- Raises:
100
- IOError: An I/O error occurred with an input or output file.
101
- InputError: The input file could not be parsed or lacked required
102
- fields.
103
-
104
- Returns:
105
- A list of dictionaries produced by parse_trans_unit().
106
- """
107
- try:
108
- results = [] # list of dictionaries (return value)
109
- names = [] # list of names of encountered keys (local variable)
110
- try:
111
- parsed_xml = minidom.parse(filename)
112
- except IOError:
113
- # Don't get caught by below handler
114
- raise
115
- except Exception as e:
116
- print()
117
- raise InputError(filename, str(e))
118
-
119
- # Make sure needed fields are present and non-empty.
120
- for trans_unit in parsed_xml.getElementsByTagName('trans-unit'):
121
- unit = _parse_trans_unit(trans_unit)
122
- for key in ['description', 'meaning', 'source']:
123
- if not key in unit or not unit[key]:
124
- raise InputError(filename + ':' + unit['key'],
125
- key + ' not found')
126
- if unit['description'].lower() == 'ibid':
127
- if unit['meaning'] not in names:
128
- # If the term has not already been described, the use of 'ibid'
129
- # is an error.
130
- raise InputError(
131
- filename,
132
- 'First encountered definition of: ' + unit['meaning']
133
- + ' has definition: ' + unit['description']
134
- + '. This error can occur if the definition was not'
135
- + ' provided on the first appearance of the message'
136
- + ' or if the source (English-language) messages differ.')
137
- else:
138
- # If term has already been described, 'ibid' was used correctly,
139
- # and we output nothing.
140
- pass
141
- else:
142
- if unit['meaning'] in names:
143
- raise InputError(filename,
144
- 'Second definition of: ' + unit['meaning'])
145
- names.append(unit['meaning'])
146
- results.append(unit)
147
-
148
- return results
149
- except IOError as e:
150
- print('Error with file {0}: {1}'.format(filename, e.strerror))
151
- sys.exit(1)
152
-
153
-
154
- def sort_units(units, templates):
155
- """Sorts the translation units by their definition order in the template.
156
-
157
- Args:
158
- units: A list of dictionaries produced by parse_trans_unit()
159
- that have a non-empty value for the key 'meaning'.
160
- templates: A string containing the Soy templates in which each of
161
- the units' meanings is defined.
162
-
163
- Returns:
164
- A new list of translation units, sorted by the order in which
165
- their meaning is defined in the templates.
166
-
167
- Raises:
168
- InputError: If a meaning definition cannot be found in the
169
- templates.
170
- """
171
- def key_function(unit):
172
- match = re.search(
173
- '\\smeaning\\s*=\\s*"{0}"\\s'.format(unit['meaning']),
174
- templates)
175
- if match:
176
- return match.start()
177
- else:
178
- raise InputError(args.templates,
179
- 'msg definition for meaning not found: ' +
180
- unit['meaning'])
181
- return sorted(units, key=key_function)
182
-
183
-
184
- def main():
185
- """Parses arguments and processes the specified file.
186
-
187
- Raises:
188
- IOError: An I/O error occurred with an input or output file.
189
- InputError: Input files lacked required fields.
190
- """
191
- # Set up argument parser.
192
- parser = argparse.ArgumentParser(description='Create translation files.')
193
- parser.add_argument(
194
- '--author',
195
- default='Ellen Spertus <ellen.spertus@gmail.com>',
196
- help='name and email address of contact for translators')
197
- parser.add_argument('--lang', default='en',
198
- help='ISO 639-1 source language code')
199
- parser.add_argument('--output_dir', default='json',
200
- help='relative directory for output files')
201
- parser.add_argument('--xlf', help='file containing xlf definitions')
202
- parser.add_argument('--templates', default=['template.soy'], nargs='+',
203
- help='relative path to Soy templates, comma or space '
204
- 'separated (used for ordering messages)')
205
- global args
206
- args = parser.parse_args()
207
-
208
- # Make sure output_dir ends with slash.
209
- if (not args.output_dir.endswith(os.path.sep)):
210
- args.output_dir += os.path.sep
211
-
212
- # Process the input file, and sort the entries.
213
- units = _process_file(args.xlf)
214
- files = []
215
- for arg in args.templates:
216
- for filename in arg.split(','):
217
- filename = filename.strip();
218
- if filename:
219
- with open(filename) as myfile:
220
- files.append(' '.join(line.strip() for line in myfile))
221
- sorted_units = sort_units(units, ' '.join(files))
222
-
223
- # Write the output files.
224
- write_files(args.author, args.lang, args.output_dir, sorted_units, True)
225
-
226
- # Delete the input .xlf file.
227
- os.remove(args.xlf)
228
- print('Removed ' + args.xlf)
229
-
230
-
231
- if __name__ == '__main__':
232
- main()