@blockdia/scratch-blocks 0.1.0
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.
- package/.nvmrc +1 -0
- package/LICENSE +674 -0
- package/README.md +57 -0
- package/TRADEMARK +1 -0
- package/blockly_compressed_horizontal.js +2268 -0
- package/blockly_compressed_vertical.js +2296 -0
- package/blockly_uncompressed_horizontal.js +1212 -0
- package/blockly_uncompressed_vertical.js +1212 -0
- package/blocks_common/colour.js +61 -0
- package/blocks_common/math.js +159 -0
- package/blocks_common/matrix.js +54 -0
- package/blocks_common/note.js +58 -0
- package/blocks_common/text.js +57 -0
- package/blocks_compressed.js +37 -0
- package/blocks_compressed_horizontal.js +68 -0
- package/blocks_compressed_vertical.js +216 -0
- package/blocks_horizontal/control.js +212 -0
- package/blocks_horizontal/default_toolbox.js +139 -0
- package/blocks_horizontal/event.js +190 -0
- package/blocks_horizontal/wedo.js +325 -0
- package/blocks_vertical/control.js +532 -0
- package/blocks_vertical/data.js +666 -0
- package/blocks_vertical/default_toolbox.js +571 -0
- package/blocks_vertical/event.js +329 -0
- package/blocks_vertical/extensions.js +294 -0
- package/blocks_vertical/looks.js +591 -0
- package/blocks_vertical/motion.js +587 -0
- package/blocks_vertical/operators.js +470 -0
- package/blocks_vertical/procedures.js +1051 -0
- package/blocks_vertical/sensing.js +555 -0
- package/blocks_vertical/sound.js +246 -0
- package/blocks_vertical/vertical_extensions.js +289 -0
- package/build/gen_blocks.js +1 -0
- package/build/test_expect.js +1 -0
- package/build/test_input.js +1 -0
- package/build.py +647 -0
- package/cleanup.sh +101 -0
- package/core/block.js +1837 -0
- package/core/block_animations.js +107 -0
- package/core/block_drag_surface.js +299 -0
- package/core/block_dragger.js +424 -0
- package/core/block_events.js +531 -0
- package/core/block_render_svg_horizontal.js +892 -0
- package/core/block_render_svg_vertical.js +1734 -0
- package/core/block_svg.js +1355 -0
- package/core/blockly.js +620 -0
- package/core/blocks.js +37 -0
- package/core/bubble.js +664 -0
- package/core/bubble_dragger.js +285 -0
- package/core/colours.js +177 -0
- package/core/comment.js +293 -0
- package/core/comment_events.js +539 -0
- package/core/connection.js +776 -0
- package/core/connection_db.js +300 -0
- package/core/constants.js +408 -0
- package/core/contextmenu.js +534 -0
- package/core/css.js +1354 -0
- package/core/data_category.js +490 -0
- package/core/dragged_connection_manager.js +260 -0
- package/core/dropdowndiv.js +408 -0
- package/core/events.js +429 -0
- package/core/events_abstract.js +113 -0
- package/core/extensions.js +450 -0
- package/core/field.js +810 -0
- package/core/field_angle.js +410 -0
- package/core/field_checkbox.js +133 -0
- package/core/field_colour.js +253 -0
- package/core/field_colour_slider.js +387 -0
- package/core/field_date.js +353 -0
- package/core/field_dropdown.js +447 -0
- package/core/field_iconmenu.js +309 -0
- package/core/field_image.js +200 -0
- package/core/field_label.js +136 -0
- package/core/field_label_serializable.js +125 -0
- package/core/field_matrix.js +566 -0
- package/core/field_note.js +850 -0
- package/core/field_number.js +366 -0
- package/core/field_numberdropdown.js +77 -0
- package/core/field_textdropdown.js +164 -0
- package/core/field_textinput.js +675 -0
- package/core/field_textinput_removable.js +105 -0
- package/core/field_variable.js +385 -0
- package/core/field_variable_getter.js +185 -0
- package/core/field_vertical_separator.js +161 -0
- package/core/flyout_base.js +935 -0
- package/core/flyout_button.js +324 -0
- package/core/flyout_dragger.js +83 -0
- package/core/flyout_extension_category_header.js +159 -0
- package/core/flyout_horizontal.js +475 -0
- package/core/flyout_vertical.js +770 -0
- package/core/generator.js +426 -0
- package/core/gesture.js +1010 -0
- package/core/grid.js +227 -0
- package/core/icon.js +205 -0
- package/core/inject.js +496 -0
- package/core/input.js +285 -0
- package/core/insertion_marker_manager.js +678 -0
- package/core/intersection_observer.js +102 -0
- package/core/msg.js +62 -0
- package/core/mutator.js +426 -0
- package/core/names.js +198 -0
- package/core/options.js +244 -0
- package/core/procedures.js +739 -0
- package/core/rendered_connection.js +417 -0
- package/core/scratch_block_comment.js +646 -0
- package/core/scratch_blocks_utils.js +246 -0
- package/core/scratch_bubble.js +699 -0
- package/core/scratch_events.js +131 -0
- package/core/scratch_msgs.js +85 -0
- package/core/scrollbar.js +875 -0
- package/core/toolbox.js +803 -0
- package/core/tooltip.js +337 -0
- package/core/touch.js +226 -0
- package/core/trashcan.js +343 -0
- package/core/ui_events.js +91 -0
- package/core/ui_menu_utils.js +68 -0
- package/core/utils.js +825 -0
- package/core/variable_events.js +259 -0
- package/core/variable_map.js +415 -0
- package/core/variable_model.js +116 -0
- package/core/variables.js +674 -0
- package/core/warning.js +199 -0
- package/core/widgetdiv.js +344 -0
- package/core/workspace.js +673 -0
- package/core/workspace_audio.js +170 -0
- package/core/workspace_comment.js +426 -0
- package/core/workspace_comment_render_svg.js +706 -0
- package/core/workspace_comment_svg.js +609 -0
- package/core/workspace_drag_surface_svg.js +195 -0
- package/core/workspace_dragger.js +132 -0
- package/core/workspace_svg.js +2382 -0
- package/core/xml.js +930 -0
- package/core/zoom_controls.js +301 -0
- package/dist/horizontal.js +222 -0
- package/dist/vertical.js +222 -0
- package/dist/web/horizontal.js +232 -0
- package/dist/web/vertical.js +232 -0
- package/i18n/common.py +234 -0
- package/i18n/create_messages.py +162 -0
- package/i18n/create_scratch_msgs.js +69 -0
- package/i18n/dedup_json.py +73 -0
- package/i18n/js_to_json.js +46 -0
- package/i18n/js_to_json.py +136 -0
- package/i18n/json_to_js.js +52 -0
- package/i18n/json_to_js.py +185 -0
- package/i18n/sync_tx_translations.js +111 -0
- package/i18n/test_scratch_msgs.js +87 -0
- package/i18n/tests.py +47 -0
- package/i18n/xliff_to_json.py +232 -0
- package/local_build.sh +70 -0
- package/media/click.mp3 +0 -0
- package/media/click.ogg +0 -0
- package/media/click.wav +0 -0
- package/media/comment-arrow-down.svg +10 -0
- package/media/comment-arrow-up.svg +10 -0
- package/media/delete-x.svg +10 -0
- package/media/delete.mp3 +0 -0
- package/media/delete.ogg +0 -0
- package/media/delete.wav +0 -0
- package/media/dropdown-arrow-dark.svg +1 -0
- package/media/dropdown-arrow.svg +1 -0
- package/media/extensions/microbit-block-icon.svg +130 -0
- package/media/extensions/music-block-icon.svg +17 -0
- package/media/extensions/pen-block-icon.svg +19 -0
- package/media/extensions/wedo2-block-icon.svg +36 -0
- package/media/eyedropper.svg +22 -0
- package/media/green-flag.svg +17 -0
- package/media/handclosed.cur +0 -0
- package/media/handdelete.cur +0 -0
- package/media/handopen.cur +0 -0
- package/media/icons/arrow.svg +12 -0
- package/media/icons/arrow_button.svg +1 -0
- package/media/icons/control_forever.svg +1 -0
- package/media/icons/control_repeat.svg +1 -0
- package/media/icons/control_stop.svg +1 -0
- package/media/icons/control_wait.svg +1 -0
- package/media/icons/event_broadcast_blue.svg +1 -0
- package/media/icons/event_broadcast_coral.svg +1 -0
- package/media/icons/event_broadcast_green.svg +1 -0
- package/media/icons/event_broadcast_magenta.svg +1 -0
- package/media/icons/event_broadcast_orange.svg +1 -0
- package/media/icons/event_broadcast_purple.svg +1 -0
- package/media/icons/event_when-broadcast-received_blue.svg +1 -0
- package/media/icons/event_when-broadcast-received_coral.svg +1 -0
- package/media/icons/event_when-broadcast-received_green.svg +1 -0
- package/media/icons/event_when-broadcast-received_magenta.svg +1 -0
- package/media/icons/event_when-broadcast-received_orange.svg +1 -0
- package/media/icons/event_when-broadcast-received_purple.svg +1 -0
- package/media/icons/event_whenflagclicked.svg +1 -0
- package/media/icons/remove.svg +19 -0
- package/media/icons/set-led_blue.svg +1 -0
- package/media/icons/set-led_coral.svg +1 -0
- package/media/icons/set-led_green.svg +1 -0
- package/media/icons/set-led_magenta.svg +1 -0
- package/media/icons/set-led_mystery.svg +1 -0
- package/media/icons/set-led_orange.svg +1 -0
- package/media/icons/set-led_purple.svg +1 -0
- package/media/icons/set-led_white.svg +1 -0
- package/media/icons/set-led_yellow.svg +1 -0
- package/media/icons/wedo_motor-clockwise.svg +1 -0
- package/media/icons/wedo_motor-counterclockwise.svg +1 -0
- package/media/icons/wedo_motor-speed_fast.svg +1 -0
- package/media/icons/wedo_motor-speed_med.svg +1 -0
- package/media/icons/wedo_motor-speed_slow.svg +1 -0
- package/media/icons/wedo_when-distance_close.svg +1 -0
- package/media/icons/wedo_when-tilt-backward.svg +1 -0
- package/media/icons/wedo_when-tilt-forward.svg +1 -0
- package/media/icons/wedo_when-tilt-left.svg +1 -0
- package/media/icons/wedo_when-tilt-right.svg +1 -0
- package/media/icons/wedo_when-tilt.svg +1 -0
- package/media/repeat.svg +18 -0
- package/media/rotate-left.svg +1 -0
- package/media/rotate-right.svg +1 -0
- package/media/sprites.png +0 -0
- package/media/status-not-ready.svg +13 -0
- package/media/status-ready.svg +13 -0
- package/media/zoom-in.svg +1 -0
- package/media/zoom-out.svg +1 -0
- package/media/zoom-reset.svg +1 -0
- package/msg/js/en.js +290 -0
- package/msg/json/en.json +285 -0
- package/msg/messages.js +359 -0
- package/msg/scratch_msgs.js +22969 -0
- package/package.json +60 -0
- package/pull_from_blockly.sh +151 -0
- package/renovate.json5 +15 -0
- package/shim/blockly_compressed_horizontal-blocks_compressed.js +1 -0
- package/shim/blockly_compressed_horizontal.Blockly.js +1 -0
- package/shim/blockly_compressed_horizontal.goog.js +1 -0
- package/shim/blockly_compressed_horizontal.js +1 -0
- package/shim/blockly_compressed_vertical-blocks_compressed.js +1 -0
- package/shim/blockly_compressed_vertical.Blockly.js +1 -0
- package/shim/blockly_compressed_vertical.goog.js +1 -0
- package/shim/blockly_compressed_vertical.js +1 -0
- package/shim/blocks_compressed_horizontal-blockly_compressed_horizontal-messages.js +1 -0
- package/shim/blocks_compressed_horizontal.js +1 -0
- package/shim/blocks_compressed_vertical-blockly_compressed_vertical-messages.js +1 -0
- package/shim/blocks_compressed_vertical.js +1 -0
- package/shim/gh-pages.js +1 -0
- package/shim/horizontal.js +1 -0
- package/shim/index.js +17 -0
- package/shim/vertical.js +1 -0
- package/universal-python.js +82 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
# Gives the translation status of the specified apps and languages.
|
|
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
|
+
"""Extracts messages from .js files into .json files for translation.
|
|
21
|
+
|
|
22
|
+
Specifically, lines with the following formats are extracted:
|
|
23
|
+
|
|
24
|
+
/// Here is a description of the following message.
|
|
25
|
+
Blockly.SOME_KEY = 'Some value';
|
|
26
|
+
|
|
27
|
+
Adjacent "///" lines are concatenated.
|
|
28
|
+
|
|
29
|
+
There are two output files, each of which is proper JSON. For each key, the
|
|
30
|
+
file en.json would get an entry of the form:
|
|
31
|
+
|
|
32
|
+
"Blockly.SOME_KEY", "Some value",
|
|
33
|
+
|
|
34
|
+
The file qqq.json would get:
|
|
35
|
+
|
|
36
|
+
"Blockly.SOME_KEY", "Here is a description of the following message.",
|
|
37
|
+
|
|
38
|
+
Commas would of course be omitted for the final entry of each value.
|
|
39
|
+
|
|
40
|
+
@author Ellen Spertus (ellen.spertus@gmail.com)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import argparse
|
|
44
|
+
import codecs
|
|
45
|
+
import json
|
|
46
|
+
import os
|
|
47
|
+
import re
|
|
48
|
+
from common import write_files
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_INPUT_DEF_PATTERN = re.compile("""Blockly.Msg.(\w*)\s*=\s*'(.*)';?\r?$""")
|
|
52
|
+
|
|
53
|
+
_INPUT_SYN_PATTERN = re.compile(
|
|
54
|
+
"""Blockly.Msg.(\w*)\s*=\s*Blockly.Msg.(\w*);""")
|
|
55
|
+
|
|
56
|
+
_CONSTANT_DESCRIPTION_PATTERN = re.compile(
|
|
57
|
+
"""{{Notranslate}}""", re.IGNORECASE)
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
# Set up argument parser.
|
|
61
|
+
parser = argparse.ArgumentParser(description='Create translation files.')
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
'--author',
|
|
64
|
+
default='Ellen Spertus <ellen.spertus@gmail.com>',
|
|
65
|
+
help='name and email address of contact for translators')
|
|
66
|
+
parser.add_argument('--lang', default='en',
|
|
67
|
+
help='ISO 639-1 source language code')
|
|
68
|
+
parser.add_argument('--output_dir', default='json',
|
|
69
|
+
help='relative directory for output files')
|
|
70
|
+
parser.add_argument('--input_file', default='messages.js',
|
|
71
|
+
help='input file')
|
|
72
|
+
parser.add_argument('--quiet', action='store_true', default=False,
|
|
73
|
+
help='only display warnings, not routine info')
|
|
74
|
+
args = parser.parse_args()
|
|
75
|
+
if (not args.output_dir.endswith(os.path.sep)):
|
|
76
|
+
args.output_dir += os.path.sep
|
|
77
|
+
|
|
78
|
+
# Read and parse input file.
|
|
79
|
+
results = []
|
|
80
|
+
synonyms = {}
|
|
81
|
+
constants = {} # Values that are constant across all languages.
|
|
82
|
+
description = ''
|
|
83
|
+
infile = codecs.open(args.input_file, 'r', 'utf-8')
|
|
84
|
+
for line in infile:
|
|
85
|
+
if line.startswith('///'):
|
|
86
|
+
if description:
|
|
87
|
+
description = description + ' ' + line[3:].strip()
|
|
88
|
+
else:
|
|
89
|
+
description = line[3:].strip()
|
|
90
|
+
else:
|
|
91
|
+
match = _INPUT_DEF_PATTERN.match(line)
|
|
92
|
+
if match:
|
|
93
|
+
key = match.group(1)
|
|
94
|
+
value = match.group(2).replace("\\'", "'")
|
|
95
|
+
if not description:
|
|
96
|
+
print('Warning: No description for ' + result['meaning'])
|
|
97
|
+
if (description and _CONSTANT_DESCRIPTION_PATTERN.search(description)):
|
|
98
|
+
constants[key] = value
|
|
99
|
+
else:
|
|
100
|
+
result = {}
|
|
101
|
+
result['meaning'] = key
|
|
102
|
+
result['source'] = value
|
|
103
|
+
result['description'] = description
|
|
104
|
+
results.append(result)
|
|
105
|
+
description = ''
|
|
106
|
+
else:
|
|
107
|
+
match = _INPUT_SYN_PATTERN.match(line)
|
|
108
|
+
if match:
|
|
109
|
+
if description:
|
|
110
|
+
print('Warning: Description preceding definition of synonym {0}.'.
|
|
111
|
+
format(match.group(1)))
|
|
112
|
+
description = ''
|
|
113
|
+
synonyms[match.group(1)] = match.group(2)
|
|
114
|
+
infile.close()
|
|
115
|
+
|
|
116
|
+
# Create <lang_file>.json, keys.json, and qqq.json.
|
|
117
|
+
write_files(args.author, args.lang, args.output_dir, results, False)
|
|
118
|
+
|
|
119
|
+
# Create synonyms.json.
|
|
120
|
+
synonym_file_name = os.path.join(os.curdir, args.output_dir, 'synonyms.json')
|
|
121
|
+
with open(synonym_file_name, 'w') as outfile:
|
|
122
|
+
json.dump(synonyms, outfile)
|
|
123
|
+
if not args.quiet:
|
|
124
|
+
print("Wrote {0} synonym pairs to {1}.".format(
|
|
125
|
+
len(synonyms), synonym_file_name))
|
|
126
|
+
|
|
127
|
+
# Create constants.json
|
|
128
|
+
constants_file_name = os.path.join(os.curdir, args.output_dir, 'constants.json')
|
|
129
|
+
with open(constants_file_name, 'w') as outfile:
|
|
130
|
+
json.dump(constants, outfile)
|
|
131
|
+
if not args.quiet:
|
|
132
|
+
print("Wrote {0} constant pairs to {1}.".format(
|
|
133
|
+
len(constants), synonym_file_name))
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const async = require('async');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const glob = require('glob');
|
|
5
|
+
|
|
6
|
+
// Globals
|
|
7
|
+
const PATH_INPUT = path.resolve(__dirname, '../msg/json/en.json');
|
|
8
|
+
// If you want to generate js files for other languages, comment out the line above,
|
|
9
|
+
// and use the one below instead.
|
|
10
|
+
// const PATH_INPUT = path.resolve(__dirname, '../msg/json/*.json');
|
|
11
|
+
const PATH_OUTPUT = path.resolve(__dirname, '../msg/js');
|
|
12
|
+
const CONCURRENCY_LIMIT = 4;
|
|
13
|
+
|
|
14
|
+
// Processing task
|
|
15
|
+
const work = function (uri, callback) {
|
|
16
|
+
fs.readFile(uri, function (err, body) {
|
|
17
|
+
const name = path.parse(uri).name;
|
|
18
|
+
if (err) return callback(err);
|
|
19
|
+
|
|
20
|
+
// Convert file body into an object (let this throw if invalid JSON)
|
|
21
|
+
body = JSON.parse(body);
|
|
22
|
+
|
|
23
|
+
// File storage object and preamble
|
|
24
|
+
let file = '';
|
|
25
|
+
file += '// This file was automatically generated. Do not modify.\n';
|
|
26
|
+
file += '\n';
|
|
27
|
+
file += '\'use strict\';\n';
|
|
28
|
+
file += '\n';
|
|
29
|
+
file += `goog.provide(\'Blockly.Msg.${name}\');\n`;
|
|
30
|
+
file += 'goog.require(\'Blockly.Msg\');\n';
|
|
31
|
+
file += '\n';
|
|
32
|
+
|
|
33
|
+
// Iterate over object and build up file
|
|
34
|
+
for (let i in body) {
|
|
35
|
+
file += `Blockly.Msg["${i}"] = "${body[i].replace(/"/g, '\\"')}";\n`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Write file to disk
|
|
39
|
+
fs.writeFile(`${PATH_OUTPUT}/${name}.js`, file, callback);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create async processing queue
|
|
44
|
+
const q = async.queue(work, CONCURRENCY_LIMIT);
|
|
45
|
+
|
|
46
|
+
// Handle errors
|
|
47
|
+
q.error = function (err) {
|
|
48
|
+
throw new Error(err);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Get all JSON files in input directory and add to queue
|
|
52
|
+
q.push(glob.sync(PATH_INPUT));
|
|
@@ -0,0 +1,185 @@
|
|
|
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()
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
|
|
36
|
+
|
|
37
|
+
let en = fs.readFileSync(path.resolve(__dirname, '../msg/json/en.json'));
|
|
38
|
+
en = JSON.parse(en);
|
|
39
|
+
const enKeys = Object.keys(en).sort().toString();
|
|
40
|
+
|
|
41
|
+
// Check that translation is valid:
|
|
42
|
+
// entry: array [key, translation] corresponding to a single string from <locale>.json
|
|
43
|
+
// - messages with placeholders have the same number of placeholders
|
|
44
|
+
// - messages must not have newlines embedded
|
|
45
|
+
const validateEntry = function (entry) {
|
|
46
|
+
const re = /(%\d)/g;
|
|
47
|
+
const [key, translation] = entry;
|
|
48
|
+
const enMatch = en[key].match(re);
|
|
49
|
+
const tMatch = translation.match(re);
|
|
50
|
+
const enCount = enMatch ? enMatch.length : 0;
|
|
51
|
+
const tCount = tMatch ? tMatch.length : 0;
|
|
52
|
+
assert.strictEqual(tCount, enCount, `${key}:${en[key]} - "${translation}" placeholder mismatch`);
|
|
53
|
+
if (enCount > 0) {
|
|
54
|
+
|
|
55
|
+
assert.notStrictEqual(tMatch, null, `${key} is missing a placeholder: ${translation}`);
|
|
56
|
+
assert.strictEqual(
|
|
57
|
+
tMatch.sort().toString(),
|
|
58
|
+
enMatch.sort().toString(),
|
|
59
|
+
`${key} is missing or has duplicate placeholders: ${translation}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
assert.strictEqual(translation.match(/[\n]/), null, `${key} contains a newline character ${translation}`);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const validate = function (json, name) {
|
|
66
|
+
assert.strictEqual(Object.keys(json).sort().toString(), enKeys, `${name}: Locale json keys do not match en.json`);
|
|
67
|
+
Object.entries(json).forEach(validateEntry);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let file = `// This file was automatically generated. Do not modify.
|
|
71
|
+
|
|
72
|
+
'use strict';
|
|
73
|
+
|
|
74
|
+
goog.provide('Blockly.ScratchMsgs.allLocales');
|
|
75
|
+
|
|
76
|
+
goog.require('Blockly.ScratchMsgs');
|
|
77
|
+
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
let localeMap = {
|
|
81
|
+
'aa-dj': 'aa_DJ',
|
|
82
|
+
'es-419': 'es_419',
|
|
83
|
+
'pt-br': 'pt_BR',
|
|
84
|
+
'zh-cn': 'zh_CN',
|
|
85
|
+
'zh-tw': 'zh_TW'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getLocaleData = async function (locale) {
|
|
89
|
+
let txLocale = localeMap[locale] || locale;
|
|
90
|
+
const data = await txPull(PROJECT, RESOURCE, txLocale, MODE);
|
|
91
|
+
return {
|
|
92
|
+
locale: locale,
|
|
93
|
+
translations: data
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
Promise.all(Object.keys(locales).map(getLocaleData)).then(function (values) {
|
|
98
|
+
values.forEach(function (translation) {
|
|
99
|
+
validate(translation.translations, translation.locale);
|
|
100
|
+
file += '\n';
|
|
101
|
+
file += `Blockly.ScratchMsgs.locales["${translation.locale}"] =\n`;
|
|
102
|
+
file += JSON.stringify(translation.translations, null, 4);
|
|
103
|
+
file += ';\n';
|
|
104
|
+
});
|
|
105
|
+
file += '// End of combined translations\n';
|
|
106
|
+
// write combined file
|
|
107
|
+
fs.writeFileSync(`${PATH_OUTPUT}/scratch_msgs.js`, file);
|
|
108
|
+
}).catch((err) => {
|
|
109
|
+
console.error(err);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
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(es.mapSync(function (str) {
|
|
62
|
+
if (!match(str)) return;
|
|
63
|
+
const result = extract(str);
|
|
64
|
+
if (!result) return;
|
|
65
|
+
if (typeof result === 'string') {
|
|
66
|
+
// locale changed or end of file, validate the current collection of keys
|
|
67
|
+
try {
|
|
68
|
+
validateKeys();
|
|
69
|
+
}
|
|
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
|
+
.pipe(es.wait(function (err) {
|
|
82
|
+
if (err) {
|
|
83
|
+
console.err(err);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
process.exit(0)
|
|
87
|
+
}));
|
package/i18n/tests.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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()
|