scratch-blocks 2.1.20 → 2.1.22

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.
@@ -43,7 +43,7 @@ import * as ScratchVariables from './variables';
43
43
  export * from 'blockly/core';
44
44
  export * from './block_reporting';
45
45
  export * from './procedures';
46
- export * from '../msg/scratch_msgs.js';
46
+ export * from './scratch_msgs';
47
47
  export * from './constants';
48
48
  export { glowStack };
49
49
  export { scratchBlocksUtils };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,iBAAiB,CAAA;AACxB,OAAO,kBAAkB,CAAA;AACzB,OAAO,eAAe,CAAA;AACtB,OAAO,gBAAgB,CAAA;AACvB,OAAO,gBAAgB,CAAA;AACvB,OAAO,eAAe,CAAA;AACtB,OAAO,iBAAiB,CAAA;AACxB,OAAO,iBAAiB,CAAA;AACxB,OAAO,eAAe,CAAA;AACtB,OAAO,oBAAoB,CAAA;AAC3B,OAAO,qBAAqB,CAAA;AAC5B,OAAO,kBAAkB,CAAA;AACzB,OAAO,gBAAgB,CAAA;AACvB,OAAO,eAAe,CAAA;AACtB,OAAO,8BAA8B,CAAA;AACrC,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,gBAAgB,MAAM,sBAAsB,CAAA;AACxD,OAAO,OAAO,CAAA;AACd,OAAO,sCAAsC,CAAA;AAC7C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,oCAAoC,CAAA;AAC3C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,yCAAyC,CAAA;AAChD,OAAO,EAA6B,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAE3F,OAAO,EAAqB,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAQlE,OAAO,wBAAwB,CAAA;AAC/B,OAAO,EAAmB,SAAS,EAAE,MAAM,SAAS,CAAA;AAEpD,OAAO,yBAAyB,CAAA;AAChC,OAAO,qBAAqB,CAAA;AAE5B,OAAO,KAAK,kBAAkB,MAAM,wBAAwB,CAAA;AAC5D,OAAO,wBAAwB,CAAA;AAC/B,OAAO,wBAAwB,CAAA;AAC/B,OAAO,8BAA8B,CAAA;AAGrC,OAAO,mBAAmB,CAAA;AAC1B,OAAO,sCAAsC,CAAA;AAC7C,OAAO,wBAAwB,CAAA;AAC/B,OAAO,0BAA0B,CAAA;AAGjC,OAAO,KAAK,gBAAgB,MAAM,aAAa,CAAA;AAE/C,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAClF,cAAc,OAAO,CAAA;AAErB,UAAU,oBAAqB,SAAQ,OAAO,CAAC,cAAc;IAC3D;;;OAGG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAA;CAClC;AASD,wBAAgB,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,wBAkDvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,iBAAiB,CAAA;AACxB,OAAO,kBAAkB,CAAA;AACzB,OAAO,eAAe,CAAA;AACtB,OAAO,gBAAgB,CAAA;AACvB,OAAO,gBAAgB,CAAA;AACvB,OAAO,eAAe,CAAA;AACtB,OAAO,iBAAiB,CAAA;AACxB,OAAO,iBAAiB,CAAA;AACxB,OAAO,eAAe,CAAA;AACtB,OAAO,oBAAoB,CAAA;AAC3B,OAAO,qBAAqB,CAAA;AAC5B,OAAO,kBAAkB,CAAA;AACzB,OAAO,gBAAgB,CAAA;AACvB,OAAO,eAAe,CAAA;AACtB,OAAO,8BAA8B,CAAA;AACrC,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,gBAAgB,MAAM,sBAAsB,CAAA;AACxD,OAAO,OAAO,CAAA;AACd,OAAO,sCAAsC,CAAA;AAC7C,OAAO,wCAAwC,CAAA;AAC/C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,oCAAoC,CAAA;AAC3C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,yCAAyC,CAAA;AAChD,OAAO,EAA6B,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAE3F,OAAO,EAAqB,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAQlE,OAAO,wBAAwB,CAAA;AAC/B,OAAO,EAAmB,SAAS,EAAE,MAAM,SAAS,CAAA;AAEpD,OAAO,yBAAyB,CAAA;AAChC,OAAO,qBAAqB,CAAA;AAE5B,OAAO,KAAK,kBAAkB,MAAM,wBAAwB,CAAA;AAC5D,OAAO,wBAAwB,CAAA;AAC/B,OAAO,wBAAwB,CAAA;AAC/B,OAAO,8BAA8B,CAAA;AAGrC,OAAO,mBAAmB,CAAA;AAC1B,OAAO,sCAAsC,CAAA;AAC7C,OAAO,wBAAwB,CAAA;AAC/B,OAAO,0BAA0B,CAAA;AAGjC,OAAO,KAAK,gBAAgB,MAAM,aAAa,CAAA;AAE/C,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAClF,cAAc,OAAO,CAAA;AAErB,UAAU,oBAAqB,SAAQ,OAAO,CAAC,cAAc;IAC3D;;;OAGG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAA;CAClC;AASD,wBAAgB,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,wBAkDvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
@@ -0,0 +1,32 @@
1
+ import en from '../msg/json/en.json';
2
+ type Messages = Record<string, string>;
3
+ /** A message key defined in this repository's English source. */
4
+ export type ScratchMessageKey = keyof typeof en;
5
+ /**
6
+ * Localized strings for Scratch blocks and workspace UI.
7
+ *
8
+ * English comes from this repository's `msg/json/en.json`, so new strings are usable before they are translated.
9
+ * Every other locale comes from scratch-l10n, which pulls reviewed translations from Transifex.
10
+ */
11
+ export declare class ScratchMsgs {
12
+ static currentLocale_: string;
13
+ static locales: Record<string, Messages>;
14
+ /**
15
+ * Apply a locale's messages to `Blockly.Msg`.
16
+ * @param locale A key of `ScratchMsgs.locales`, such as `'de'` or `'pt-br'`.
17
+ */
18
+ static setLocale(locale: string): void;
19
+ /**
20
+ * Look up a message without changing the current locale.
21
+ * @param msgId The message key.
22
+ * @param defaultMsg Returned when the locale has no translation for `msgId`.
23
+ * @param useLocale The locale to use instead of the current one.
24
+ * @returns The translation, else `defaultMsg`, else the English message.
25
+ */
26
+ static translate(msgId: ScratchMessageKey, defaultMsg?: string, useLocale?: string): string;
27
+ static translate(msgId: string, defaultMsg: string, useLocale?: string): string;
28
+ static translate(msgId: string, defaultMsg?: string, useLocale?: string): string | undefined;
29
+ private static getLocaleMessages;
30
+ }
31
+ export {};
32
+ //# sourceMappingURL=scratch_msgs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scratch_msgs.d.ts","sourceRoot":"","sources":["../../../src/scratch_msgs.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEpC,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEtC,iEAAiE;AACjE,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,EAAE,CAAA;AAY/C;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,cAAc,SAAO;IAC5B,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAwB;IAEhE;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAetC;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,iBAAiB,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAC3F,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAC/E,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK5F,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAGjC"}
package/eslint.config.mjs CHANGED
@@ -30,7 +30,6 @@ export default eslintConfigScratch.defineConfig(
30
30
  '*_uncompressed*.js',
31
31
  'msg/**',
32
32
  'core/css.js',
33
- 'i18n/**',
34
33
  'tests/jsunit/**',
35
34
  'tests/workspace_svg/**',
36
35
  'tests/blocks/**',
package/msg/json/en.json CHANGED
@@ -245,12 +245,18 @@
245
245
  "DELETE_X_BLOCKS": "Delete %1 Blocks",
246
246
  "DELETE_ALL_BLOCKS": "Delete all %1 blocks?",
247
247
  "CLEAN_UP": "Clean up Blocks",
248
+ "COLLAPSE_BLOCK": "Collapse Block",
249
+ "EXPAND_BLOCK": "Expand Block",
250
+ "COLLAPSE_ALL": "Collapse Blocks",
251
+ "EXPAND_ALL": "Expand Blocks",
252
+ "COLLAPSED_WARNINGS_WARNING": "Collapsed blocks contain warnings.",
248
253
  "HELP": "Help",
249
254
  "UNDO": "Undo",
250
255
  "REDO": "Redo",
251
256
  "EDIT_PROCEDURE": "Edit",
252
257
  "SHOW_PROCEDURE_DEFINITION": "Go to definition",
253
258
  "WORKSPACE_COMMENT_DEFAULT_TEXT": "Say something...",
259
+ "WORKSPACE_ARIA_LABEL": "Code Area",
254
260
  "COLOUR_HUE_LABEL": "Color",
255
261
  "COLOUR_SATURATION_LABEL": "Saturation",
256
262
  "COLOUR_BRIGHTNESS_LABEL": "Brightness",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratch-blocks",
3
- "version": "2.1.20",
3
+ "version": "2.1.22",
4
4
  "description": "Scratch Blocks is a library for building creative computing interfaces.",
5
5
  "author": "Massachusetts Institute of Technology",
6
6
  "license": "Apache-2.0",
@@ -21,6 +21,8 @@
21
21
  "scripts": {
22
22
  "build": "webpack --mode production",
23
23
  "format": "prettier --write . && eslint --fix",
24
+ "i18n:push": "tx-push-src scratch-editor blocks msg/json/en.json",
25
+ "i18n:src": "node scripts/validate-en-json.mjs",
24
26
  "prepare": "husky || true",
25
27
  "start": "webpack serve --open --mode development",
26
28
  "test": "vitest run",
@@ -41,10 +43,11 @@
41
43
  "@vitest/browser": "4.1.11",
42
44
  "@vitest/browser-playwright": "4.1.11",
43
45
  "eslint": "9.39.5",
44
- "eslint-config-scratch": "14.1.79",
46
+ "eslint-config-scratch": "14.1.81",
45
47
  "husky": "9.1.7",
46
48
  "playwright": "1.63.0",
47
49
  "prettier": "3.8.5",
50
+ "scratch-l10n": "6.1.119",
48
51
  "scratch-semantic-release-config": "4.0.1",
49
52
  "semantic-release": "25.0.9",
50
53
  "source-map-loader": "5.0.0",
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright 2026 Scratch Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ *
6
+ * Checks msg/json/en.json, the English source pushed to Transifex, before it is pushed.
7
+ */
8
+ import console from 'node:console'
9
+ import fs from 'node:fs'
10
+ import process from 'node:process'
11
+ import { URL, fileURLToPath } from 'node:url'
12
+
13
+ const file = process.argv[2] ?? fileURLToPath(new URL('../msg/json/en.json', import.meta.url))
14
+
15
+ const messages = JSON.parse(fs.readFileSync(file, 'utf8'))
16
+ const errors = []
17
+ for (const [key, value] of Object.entries(messages)) {
18
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key)) errors.push(`${key}: key must be UPPER_SNAKE_CASE`)
19
+ if (typeof value !== 'string' || !value.trim()) errors.push(`${key}: value must be a non-empty string`)
20
+ else if (value.includes('\n')) errors.push(`${key}: value must not contain a newline`)
21
+ }
22
+
23
+ if (errors.length > 0) {
24
+ console.error(`${file}:\n ${errors.join('\n ')}`)
25
+ process.exit(1)
26
+ }
27
+ console.log(`${file}: ${Object.keys(messages).length} messages OK`)
@@ -21,8 +21,8 @@
21
21
  * @author fraser@google.com (Neil Fraser)
22
22
  */
23
23
  import * as Blockly from 'blockly/core'
24
- import { ScratchMsgs } from '../../msg/scratch_msgs.js'
25
24
  import * as Constants from '../constants'
25
+ import { ScratchMsgs } from '../scratch_msgs'
26
26
  import type { ScratchVariableModel } from '../scratch_variable_model'
27
27
  import { createVariable, renameVariable } from '../variables'
28
28
 
package/src/index.ts CHANGED
@@ -63,7 +63,7 @@ import * as ScratchVariables from './variables'
63
63
  export * from 'blockly/core'
64
64
  export * from './block_reporting'
65
65
  export * from './procedures'
66
- export * from '../msg/scratch_msgs.js'
66
+ export * from './scratch_msgs'
67
67
  export * from './constants'
68
68
  export { glowStack }
69
69
  export { scratchBlocksUtils }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Copyright 2026 Scratch Foundation
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import * as Blockly from 'blockly/core'
6
+ import blocksMsgs from 'scratch-l10n/locales/blocks-msgs.js'
7
+ import en from '../msg/json/en.json'
8
+
9
+ type Messages = Record<string, string>
10
+
11
+ /** A message key defined in this repository's English source. */
12
+ export type ScratchMessageKey = keyof typeof en
13
+
14
+ /**
15
+ * Blockly core message keys that read the same text as a Scratch message, mapped to that Scratch key. Transifex
16
+ * holds the translation under the Scratch key.
17
+ */
18
+ const BLOCKLY_ALIASES: Record<string, ScratchMessageKey> = {
19
+ // The Duplicate items in the block and workspace comment context menus.
20
+ DUPLICATE_BLOCK: 'DUPLICATE',
21
+ DUPLICATE_COMMENT: 'DUPLICATE',
22
+ }
23
+
24
+ /**
25
+ * Localized strings for Scratch blocks and workspace UI.
26
+ *
27
+ * English comes from this repository's `msg/json/en.json`, so new strings are usable before they are translated.
28
+ * Every other locale comes from scratch-l10n, which pulls reviewed translations from Transifex.
29
+ */
30
+ export class ScratchMsgs {
31
+ static currentLocale_ = 'en'
32
+ static locales: Record<string, Messages> = { ...blocksMsgs, en }
33
+
34
+ /**
35
+ * Apply a locale's messages to `Blockly.Msg`.
36
+ * @param locale A key of `ScratchMsgs.locales`, such as `'de'` or `'pt-br'`.
37
+ */
38
+ static setLocale(locale: string): void {
39
+ const messages = this.getLocaleMessages(locale)
40
+ if (!messages) {
41
+ console.warn('Ignoring unrecognized locale: ' + locale)
42
+ return
43
+ }
44
+ this.currentLocale_ = locale
45
+ // Apply English first so any key this locale lacks falls back to English instead of keeping the previous
46
+ // locale's text.
47
+ Object.assign(Blockly.Msg, this.locales.en, messages)
48
+ for (const [blocklyKey, scratchKey] of Object.entries(BLOCKLY_ALIASES)) {
49
+ Blockly.Msg[blocklyKey] = Blockly.Msg[scratchKey]
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Look up a message without changing the current locale.
55
+ * @param msgId The message key.
56
+ * @param defaultMsg Returned when the locale has no translation for `msgId`.
57
+ * @param useLocale The locale to use instead of the current one.
58
+ * @returns The translation, else `defaultMsg`, else the English message.
59
+ */
60
+ static translate(msgId: ScratchMessageKey, defaultMsg?: string, useLocale?: string): string
61
+ static translate(msgId: string, defaultMsg: string, useLocale?: string): string
62
+ static translate(msgId: string, defaultMsg?: string, useLocale?: string): string | undefined
63
+ static translate(msgId: string, defaultMsg?: string, useLocale?: string): string | undefined {
64
+ return this.getLocaleMessages(useLocale ?? this.currentLocale_)?.[msgId] ?? defaultMsg ?? this.locales.en[msgId]
65
+ }
66
+
67
+ private static getLocaleMessages(locale: string): Messages | undefined {
68
+ return Object.prototype.hasOwnProperty.call(this.locales, locale) ? this.locales[locale] : undefined
69
+ }
70
+ }
package/tsconfig.json CHANGED
@@ -7,6 +7,7 @@
7
7
  /* Modules */
8
8
  "module": "esnext",
9
9
  "moduleResolution": "bundler",
10
+ "resolveJsonModule": true,
10
11
 
11
12
  /* Emit */
12
13
  "declaration": true,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright 2026 Scratch Foundation
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ declare module 'scratch-l10n' {
6
+ /** The locales Scratch supports, keyed by locale code. */
7
+ const locales: Record<string, { name: string }>
8
+ export default locales
9
+ }
10
+
11
+ declare module 'scratch-l10n/locales/blocks-msgs.js' {
12
+ /** Reviewed block translations from Transifex, keyed by locale, then by message key. */
13
+ const blocksMsgs: Record<string, Record<string, string>>
14
+ export default blocksMsgs
15
+ }
@@ -1,9 +0,0 @@
1
- export class ScratchMsgs {
2
- static currentLocale_: string;
3
- static setLocale(locale: any): void;
4
- static translate(msgId: any, defaultMsg: any, useLocale: any): any;
5
- }
6
- export namespace ScratchMsgs {
7
- let locales: typeof locales;
8
- }
9
- //# sourceMappingURL=scratch_msgs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scratch_msgs.d.ts","sourceRoot":"","sources":["../../../msg/scratch_msgs.js"],"names":[],"mappings":"AAEA;IACE,8BAA4B;IAG5B,oCAQC;IAED,mEAUC;CACF"}
package/i18n/common.py DELETED
@@ -1,234 +0,0 @@
1
- #!/usr/bin/python
2
-
3
- # Code shared by translation conversion scripts.
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 codecs
21
- import json
22
- import os
23
- from datetime import datetime
24
-
25
- class InputError(Exception):
26
- """Exception raised for errors in the input.
27
-
28
- Attributes:
29
- location -- where error occurred
30
- msg -- explanation of the error
31
-
32
- """
33
-
34
- def __init__(self, location, msg):
35
- Exception.__init__(self, '{0}: {1}'.format(location, msg))
36
- self.location = location
37
- self.msg = msg
38
-
39
-
40
- def read_json_file(filename):
41
- """Read a JSON file as UTF-8 into a dictionary, discarding @metadata.
42
-
43
- Args:
44
- filename: The filename, which must end ".json".
45
-
46
- Returns:
47
- The dictionary.
48
-
49
- Raises:
50
- InputError: The filename did not end with ".json" or an error occurred
51
- while opening or reading the file.
52
- """
53
- if not filename.endswith('.json'):
54
- raise InputError(filename, 'filenames must end with ".json"')
55
- try:
56
- # Read in file.
57
- with codecs.open(filename, 'r', 'utf-8') as infile:
58
- defs = json.load(infile)
59
- if '@metadata' in defs:
60
- del defs['@metadata']
61
- return defs
62
- except ValueError as e:
63
- print('Error reading ' + filename)
64
- raise InputError(filename, str(e))
65
-
66
-
67
- def _create_qqq_file(output_dir):
68
- """Creates a qqq.json file with message documentation for translatewiki.net.
69
-
70
- The file consists of key-value pairs, where the keys are message ids and
71
- the values are descriptions for the translators of the messages.
72
- What documentation exists for the format can be found at:
73
- http://translatewiki.net/wiki/Translating:Localisation_for_developers#Message_documentation
74
-
75
- The file should be closed by _close_qqq_file().
76
-
77
- Parameters:
78
- output_dir: The output directory.
79
-
80
- Returns:
81
- A pointer to a file to which a left brace and newline have been written.
82
-
83
- Raises:
84
- IOError: An error occurred while opening or writing the file.
85
- """
86
- qqq_file_name = os.path.join(os.curdir, output_dir, 'qqq.json')
87
- qqq_file = codecs.open(qqq_file_name, 'w', 'utf-8')
88
- print('Created file: ' + qqq_file_name)
89
- qqq_file.write('{\n')
90
- return qqq_file
91
-
92
-
93
- def _close_qqq_file(qqq_file):
94
- """Closes a qqq.json file created and opened by _create_qqq_file().
95
-
96
- This writes the final newlines and right brace.
97
-
98
- Args:
99
- qqq_file: A file created by _create_qqq_file().
100
-
101
- Raises:
102
- IOError: An error occurred while writing to or closing the file.
103
- """
104
- qqq_file.write('\n}\n')
105
- qqq_file.close()
106
-
107
-
108
- def _create_lang_file(author, lang, output_dir):
109
- """Creates a <lang>.json file for translatewiki.net.
110
-
111
- The file consists of metadata, followed by key-value pairs, where the keys
112
- are message ids and the values are the messages in the language specified
113
- by the corresponding command-line argument. The file should be closed by
114
- _close_lang_file().
115
-
116
- Args:
117
- author: Name and email address of contact for translators.
118
- lang: ISO 639-1 source language code.
119
- output_dir: Relative directory for output files.
120
-
121
- Returns:
122
- A pointer to a file to which the metadata has been written.
123
-
124
- Raises:
125
- IOError: An error occurred while opening or writing the file.
126
- """
127
- lang_file_name = os.path.join(os.curdir, output_dir, lang + '.json')
128
- lang_file = codecs.open(lang_file_name, 'w', 'utf-8')
129
- print('Created file: ' + lang_file_name)
130
- # string.format doesn't like printing braces, so break up our writes.
131
- lang_file.write('{\n\t"@metadata": {')
132
- lang_file.write("""
133
- \t\t"author": "{0}",
134
- \t\t"lastupdated": "{1}",
135
- \t\t"locale": "{2}",
136
- \t\t"messagedocumentation" : "qqq"
137
- """.format(author, str(datetime.now()), lang))
138
- lang_file.write('\t},\n')
139
- return lang_file
140
-
141
-
142
- def _close_lang_file(lang_file):
143
- """Closes a <lang>.json file created with _create_lang_file().
144
-
145
- This also writes the terminating left brace and newline.
146
-
147
- Args:
148
- lang_file: A file opened with _create_lang_file().
149
-
150
- Raises:
151
- IOError: An error occurred while writing to or closing the file.
152
- """
153
- lang_file.write('\n}\n')
154
- lang_file.close()
155
-
156
-
157
- def _create_key_file(output_dir):
158
- """Creates a keys.json file mapping Closure keys to Blockly keys.
159
-
160
- Args:
161
- output_dir: Relative directory for output files.
162
-
163
- Raises:
164
- IOError: An error occurred while creating the file.
165
- """
166
- key_file_name = os.path.join(os.curdir, output_dir, 'keys.json')
167
- key_file = open(key_file_name, 'w')
168
- key_file.write('{\n')
169
- print('Created file: ' + key_file_name)
170
- return key_file
171
-
172
-
173
- def _close_key_file(key_file):
174
- """Closes a key file created and opened with _create_key_file().
175
-
176
- Args:
177
- key_file: A file created by _create_key_file().
178
-
179
- Raises:
180
- IOError: An error occurred while writing to or closing the file.
181
- """
182
- key_file.write('\n}\n')
183
- key_file.close()
184
-
185
-
186
- def write_files(author, lang, output_dir, units, write_key_file):
187
- """Writes the output files for the given units.
188
-
189
- There are three possible output files:
190
- * lang_file: JSON file mapping meanings (e.g., Maze.turnLeft) to the
191
- English text. The base name of the language file is specified by the
192
- "lang" command-line argument.
193
- * key_file: JSON file mapping meanings to Soy-generated keys (long hash
194
- codes). This is only output if the parameter write_key_file is True.
195
- * qqq_file: JSON file mapping meanings to descriptions.
196
-
197
- Args:
198
- author: Name and email address of contact for translators.
199
- lang: ISO 639-1 source language code.
200
- output_dir: Relative directory for output files.
201
- units: A list of dictionaries with entries for 'meaning', 'source',
202
- 'description', and 'keys' (the last only if write_key_file is true),
203
- in the order desired in the output files.
204
- write_key_file: Whether to output a keys.json file.
205
-
206
- Raises:
207
- IOError: An error occurs opening, writing to, or closing a file.
208
- KeyError: An expected key is missing from units.
209
- """
210
- lang_file = _create_lang_file(author, lang, output_dir)
211
- qqq_file = _create_qqq_file(output_dir)
212
- if write_key_file:
213
- key_file = _create_key_file(output_dir)
214
- first_entry = True
215
- for unit in units:
216
- if not first_entry:
217
- lang_file.write(',\n')
218
- if write_key_file:
219
- key_file.write(',\n')
220
- qqq_file.write(',\n')
221
- lang_file.write(u'\t"{0}": "{1}"'.format(
222
- unit['meaning'],
223
- unit['source'].replace('"', "'")))
224
- if write_key_file:
225
- key_file.write('"{0}": "{1}"'.format(unit['meaning'], unit['key']))
226
- qqq_file.write(u'\t"{0}": "{1}"'.format(
227
- unit['meaning'],
228
- unit['description'].replace('"', "'").replace(
229
- '{lb}', '{').replace('{rb}', '}')))
230
- first_entry = False
231
- _close_lang_file(lang_file)
232
- if write_key_file:
233
- _close_key_file(key_file)
234
- _close_qqq_file(qqq_file)
@@ -1,162 +0,0 @@
1
- #!/usr/bin/python
2
-
3
- # Generate .js files defining Blockly core and language messages.
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
22
- import os
23
- import re
24
- import sys
25
- from common import read_json_file
26
-
27
-
28
- _NEWLINE_PATTERN = re.compile('[\n\r]')
29
-
30
-
31
- def string_is_ascii(s):
32
- # This approach is better for compatibility
33
- return all(ord(c) < 128 for c in s)
34
-
35
- def load_constants(filename):
36
- """Read in constants file, which must be output in every language."""
37
- constant_defs = read_json_file(filename);
38
- constants_text = '\n'
39
- for key in constant_defs:
40
- value = constant_defs[key]
41
- value = value.replace('"', '\\"')
42
- constants_text += '\nBlockly.Msg["{0}"] = "{1}";'.format(key, value)
43
- return constants_text
44
-
45
- def main():
46
- """Generate .js files defining Blockly core and language messages."""
47
-
48
- # Process command-line arguments.
49
- parser = argparse.ArgumentParser(description='Convert JSON files to JS.')
50
- parser.add_argument('--source_lang', default='en',
51
- help='ISO 639-1 source language code')
52
- parser.add_argument('--source_lang_file',
53
- default=os.path.join('json', 'en.json'),
54
- help='Path to .json file for source language')
55
- parser.add_argument('--source_synonym_file',
56
- default=os.path.join('json', 'synonyms.json'),
57
- help='Path to .json file with synonym definitions')
58
- parser.add_argument('--source_constants_file',
59
- default=os.path.join('json', 'constants.json'),
60
- help='Path to .json file with constant definitions')
61
- parser.add_argument('--output_dir', default='js/',
62
- help='relative directory for output files')
63
- parser.add_argument('--key_file', default='keys.json',
64
- help='relative path to input keys file')
65
- parser.add_argument('--quiet', action='store_true', default=False,
66
- help='do not write anything to standard output')
67
- parser.add_argument('files', nargs='+', help='input files')
68
- args = parser.parse_args()
69
- if not args.output_dir.endswith(os.path.sep):
70
- args.output_dir += os.path.sep
71
-
72
- # Read in source language .json file, which provides any values missing
73
- # in target languages' .json files.
74
- source_defs = read_json_file(os.path.join(os.curdir, args.source_lang_file))
75
- # Make sure the source file doesn't contain a newline or carriage return.
76
- for key, value in source_defs.items():
77
- if _NEWLINE_PATTERN.search(value):
78
- print('ERROR: definition of {0} in {1} contained a newline character.'.
79
- format(key, args.source_lang_file))
80
- sys.exit(1)
81
- sorted_keys = sorted(source_defs.keys())
82
-
83
- # Read in synonyms file, which must be output in every language.
84
- synonym_defs = read_json_file(os.path.join(
85
- os.curdir, args.source_synonym_file))
86
- # synonym_defs is also being sorted to ensure the same order is kept
87
- synonym_text = '\n'.join(['Blockly.Msg.{0} = Blockly.Msg.{1};'.format(
88
- key, synonym_defs[key]) for key in sorted(synonym_defs)])
89
-
90
- # Read in constants file, which must be output in every language.
91
- constants_text = load_constants(os.path.join(os.curdir, args.source_constants_file))
92
-
93
- # Create each output file.
94
- for arg_file in args.files:
95
- (_, filename) = os.path.split(arg_file)
96
- target_lang = filename[:filename.index('.')]
97
- if target_lang not in ('qqq', 'keys', 'synonyms', 'constants'):
98
- target_defs = read_json_file(os.path.join(os.curdir, arg_file))
99
-
100
- # Verify that keys are 'ascii'
101
- bad_keys = [key for key in target_defs if not string_is_ascii(key)]
102
- if bad_keys:
103
- print(u'These keys in {0} contain non ascii characters: {1}'.format(
104
- filename, ', '.join(bad_keys)))
105
-
106
- # If there's a '\n' or '\r', remove it and print a warning.
107
- for key, value in target_defs.items():
108
- if _NEWLINE_PATTERN.search(value):
109
- print(u'WARNING: definition of {0} in {1} contained '
110
- 'a newline character.'.
111
- format(key, arg_file))
112
- target_defs[key] = _NEWLINE_PATTERN.sub(' ', value)
113
-
114
- # Output file.
115
- outname = os.path.join(os.curdir, args.output_dir, target_lang + '.js')
116
- with codecs.open(outname, 'w', 'utf-8') as outfile:
117
- outfile.write(
118
- """// This file was automatically generated. Do not modify.
119
-
120
- 'use strict';
121
-
122
- goog.provide('Blockly.Msg.{0}');
123
-
124
- goog.require('Blockly.Msg');
125
-
126
- """.format(target_lang.replace('-', '.')))
127
- # For each key in the source language file, output the target value
128
- # if present; otherwise, output the source language value with a
129
- # warning comment.
130
- for key in sorted_keys:
131
- if key in target_defs:
132
- value = target_defs[key]
133
- comment = ''
134
- del target_defs[key]
135
- else:
136
- value = source_defs[key]
137
- comment = ' // untranslated'
138
- value = value.replace('"', '\\"')
139
- outfile.write(u'Blockly.Msg["{0}"] = "{1}";{2}\n'.format(
140
- key, value, comment))
141
-
142
- # Announce any keys defined only for target language.
143
- if target_defs:
144
- extra_keys = [key for key in target_defs if key not in synonym_defs]
145
- synonym_keys = [key for key in target_defs if key in synonym_defs]
146
- if not args.quiet:
147
- if extra_keys:
148
- print(u'These extra keys appeared in {0}: {1}'.format(
149
- filename, ', '.join(extra_keys)))
150
- if synonym_keys:
151
- print(u'These synonym keys appeared in {0}: {1}'.format(
152
- filename, ', '.join(synonym_keys)))
153
-
154
- outfile.write(synonym_text)
155
- outfile.write(constants_text)
156
-
157
- if not args.quiet:
158
- print('Created {0}.'.format(outname))
159
-
160
-
161
- if __name__ == '__main__':
162
- main()