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
@@ -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"}
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/renderer/renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,OAAO,CAAC,KAAK,CAAC,QAAQ;IACzD;;;;OAIG;IACM,YAAY,IAAI,MAAM;IAI/B;;;;;;OAMG;IACM,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,UAAU,GAAG,MAAM;IAI9F;;;;OAIG;IACM,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,GAAG,UAAU;IAI7D;;;OAGG;IACM,cAAc,IAAI,gBAAgB;IAI3C;;;;;OAKG;IACM,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU;IAItF;;;;OAIG;IACM,yBAAyB,CAAC,UAAU,EAAE,OAAO,CAAC,kBAAkB,GAAG,OAAO;CAGpF"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/renderer/renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,OAAO,CAAC,KAAK,CAAC,QAAQ;IACzD;;;;OAIG;IACM,YAAY,IAAI,MAAM;IAI/B;;;;;;OAMG;IACM,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,UAAU,GAAG,MAAM;IAI9F;;;;OAIG;IACM,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,GAAG,UAAU;IAI7D;;;OAGG;IACM,cAAc,IAAI,gBAAgB;IAI3C;;;;;OAKG;IACM,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU;IAItF;;;;OAIG;IACM,yBAAyB,CAAC,UAAU,EAAE,OAAO,CAAC,kBAAkB,GAAG,OAAO;CAIpF"}
@@ -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.19",
3
+ "version": "2.1.21",
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",
@@ -36,24 +38,25 @@
36
38
  "blockly": "^12.4.1"
37
39
  },
38
40
  "devDependencies": {
39
- "@commitlint/cli": "20.5.0",
40
- "@commitlint/config-conventional": "20.5.0",
41
- "@vitest/browser": "4.1.5",
42
- "@vitest/browser-playwright": "4.1.5",
43
- "eslint": "9.39.4",
44
- "eslint-config-scratch": "14.1.12",
41
+ "@commitlint/cli": "20.5.3",
42
+ "@commitlint/config-conventional": "20.5.3",
43
+ "@vitest/browser": "4.1.11",
44
+ "@vitest/browser-playwright": "4.1.11",
45
+ "eslint": "9.39.5",
46
+ "eslint-config-scratch": "14.1.81",
45
47
  "husky": "9.1.7",
46
- "playwright": "1.59.1",
47
- "prettier": "3.8.3",
48
+ "playwright": "1.63.0",
49
+ "prettier": "3.8.5",
50
+ "scratch-l10n": "6.1.119",
48
51
  "scratch-semantic-release-config": "4.0.1",
49
- "semantic-release": "25.0.3",
52
+ "semantic-release": "25.0.9",
50
53
  "source-map-loader": "5.0.0",
51
- "ts-loader": "9.5.7",
54
+ "ts-loader": "9.6.2",
52
55
  "typescript": "5.9.3",
53
- "vitest": "4.1.5",
54
- "webpack": "5.106.2",
56
+ "vitest": "4.1.11",
57
+ "webpack": "5.111.1",
55
58
  "webpack-cli": "6.0.1",
56
- "webpack-dev-server": "5.2.3"
59
+ "webpack-dev-server": "5.2.6"
57
60
  },
58
61
  "config": {
59
62
  "commitizen": {
@@ -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
 
@@ -47,7 +47,7 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
47
47
  // dropdownCreate returns MenuOption[] rather than Blockly.MenuGenerator's
48
48
  // MenuOption[][] variant; the cast is needed to satisfy FieldVariable's
49
49
  // menuGenerator_ type while the actual runtime shape is compatible.
50
- this.menuGenerator_ = ScratchFieldVariable.dropdownCreate.bind(this) as unknown as Blockly.MenuGenerator
50
+ this.menuGenerator_ = ScratchFieldVariable.dropdownCreate.bind(this)
51
51
  }
52
52
 
53
53
  initModel() {
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 }
@@ -159,7 +159,7 @@ export function isContentNodeFocused(): boolean {
159
159
  return Blockly.getFocusManager().getFocusedNode() !== null
160
160
  }
161
161
 
162
- ;(registerContinuousToolbox as () => void)()
162
+ registerContinuousToolbox()
163
163
  Blockly.Scrollbar.scrollbarThickness = Blockly.Touch.TOUCH_ENABLED ? 14 : 11
164
164
  Blockly.FlyoutButton.TEXT_MARGIN_X = 40
165
165
  Blockly.FlyoutButton.TEXT_MARGIN_Y = 10
@@ -52,7 +52,7 @@ export class ConstantProvider extends Blockly.zelos.ConstantProvider {
52
52
  * @returns The shape object for the given connection.
53
53
  */
54
54
  override shapeFor(connection: Blockly.RenderedConnection): ReturnType<Blockly.zelos.ConstantProvider['shapeFor']> {
55
- const connectionType = connection.type as Blockly.ConnectionType
55
+ const connectionType: Blockly.ConnectionType = connection.type
56
56
  let checks = connection.getCheck()
57
57
  const hexagonal = this.HEXAGONAL
58
58
  const rounded = this.ROUNDED
@@ -64,7 +64,7 @@ export class Drawer extends Blockly.zelos.Drawer {
64
64
  */
65
65
  override drawConnectionHighlightPath(measurable: Blockly.blockRendering.Connection) {
66
66
  const conn = measurable.connectionModel
67
- const connectionType = conn.type as Blockly.ConnectionType
67
+ const connectionType: Blockly.ConnectionType = conn.type
68
68
  if (connectionType === Blockly.ConnectionType.INPUT_VALUE && measurable.isDynamicShape) {
69
69
  const input = measurable as Blockly.blockRendering.InlineInput
70
70
  const EXPAND_X = 0.5
@@ -65,7 +65,8 @@ export class ScratchRenderer extends Blockly.zelos.Renderer {
65
65
  * @returns True if we should highlight the connection.
66
66
  */
67
67
  override shouldHighlightConnection(connection: Blockly.RenderedConnection): boolean {
68
- return (connection.type as Blockly.ConnectionType) === Blockly.ConnectionType.INPUT_VALUE
68
+ const connectionType: Blockly.ConnectionType = connection.type
69
+ return connectionType === Blockly.ConnectionType.INPUT_VALUE
69
70
  }
70
71
  }
71
72
 
@@ -28,7 +28,8 @@ Blockly.Connection.getConnectionForOrphanedConnection = function (
28
28
  // Apply the wrapping logic when the orphaned connection is a statement
29
29
  // connection (PREVIOUS_STATEMENT). Value connections (OUTPUT_VALUE) are
30
30
  // already handled by the original implementation.
31
- if ((orphanConnection.type as Blockly.ConnectionType) === Blockly.ConnectionType.PREVIOUS_STATEMENT) {
31
+ const orphanType: Blockly.ConnectionType = orphanConnection.type
32
+ if (orphanType === Blockly.ConnectionType.PREVIOUS_STATEMENT) {
32
33
  const checker = orphanConnection.getConnectionChecker()
33
34
  for (const input of startBlock.inputList) {
34
35
  const conn = input.connection
@@ -87,7 +88,7 @@ Reflect.set(
87
88
  if (markerPreviousConnection?.isConnected() && !markerNextConnection?.isConnected()) {
88
89
  for (const input of marker.inputList) {
89
90
  const conn = input.connection
90
- const connType = conn?.type as Blockly.ConnectionType | undefined
91
+ const connType = conn?.type
91
92
  if (connType !== Blockly.ConnectionType.NEXT_STATEMENT || !conn?.isConnected()) {
92
93
  continue
93
94
  }
@@ -69,7 +69,7 @@ export class ScratchCommentBubble
69
69
  if (gesture) {
70
70
  // ScratchCommentBubble implements IBubble structurally but TypeScript
71
71
  // cannot verify it because IBubble.drag has a different signature here.
72
- gesture.handleBubbleStart(e, this as unknown as Blockly.IBubble)
72
+ gesture.handleBubbleStart(e, this)
73
73
  Blockly.common.setSelected(this)
74
74
  }
75
75
  }
@@ -50,8 +50,9 @@ class ScratchConnectionChecker extends Blockly.ConnectionChecker {
50
50
  // getConnectionForOrphanedConnection patch routes displaced blocks into
51
51
  // statement inputs, so we allow the connection when a suitable statement
52
52
  // input exists on the dragging block.
53
+ const bType: Blockly.ConnectionType = b.type
53
54
  if (
54
- (b.type as Blockly.ConnectionType) === Blockly.ConnectionType.NEXT_STATEMENT &&
55
+ bType === Blockly.ConnectionType.NEXT_STATEMENT &&
55
56
  b.isConnected() &&
56
57
  !(a.getSourceBlock() as Blockly.Block).nextConnection
57
58
  ) {
@@ -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)