Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions packages/blockly/gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import {
import {
build,
buildAdvancedCompilationTest,
langfiles,
messages,
minify,
} from './scripts/gulpfiles/build_tasks.mjs';
import {cleanBuildDir, cleanReleaseDir} from './scripts/lib/fs_utils.mjs';
Expand All @@ -42,7 +40,6 @@ export default build;
//
// prettier-ignore
export {
langfiles,
minify,
build,
prepareDemos,
Expand All @@ -55,7 +52,6 @@ export {
//
// prettier-ignore
export {
messages, // Generate msg/json/en.json et al.
clean,
buildAdvancedCompilationTest,
}
6 changes: 3 additions & 3 deletions packages/blockly/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,18 @@
}
},
"scripts": {
"build": "npm run tsc && gulp build",
"build": "npm run tsc && gulp build && npm run langfiles",
"build-debug": "gulp build --verbose --debug",
"build-debug-log": "npm run build:debug > build-debug.log 2>&1 && tail -3 build-debug.log",
"build-strict": "gulp build --verbose --strict",
"build-strict-log": "npm run build:strict > build-debug.log 2>&1 && tail -3 build-debug.log",
"clean": "gulp clean",
"deployDemos": "npm ci && gulp deployDemos",
"deployDemos:beta": "npm ci && gulp deployDemosBeta",
"messages": "gulp messages",
"messages": "node \"scripts/messages.mjs\"",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"langfiles": "gulp langfiles",
"langfiles": "node \"scripts/langfiles.mjs\"",
"minify": "gulp minify",
"package": "node scripts/package.mjs",
"prepareDemos": "gulp prepareDemos",
Expand Down
7 changes: 7 additions & 0 deletions packages/blockly/scripts/build_constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ export const TEST_TSC_OUTPUT_DIR = path.join(BUILD_DIR, 'tests');
// Directory in which to assemble (and from which to publish) the
// blockly npm package.
export const RELEASE_DIR = 'dist';

/**
* Path to the python runtime.
* This will normalize the command across platforms (e.g. python3 on Linux and
* Mac, python on Windows).
*/
export const PYTHON = process.platform === 'win32' ? 'python' : 'python3';
124 changes: 1 addition & 123 deletions packages/blockly/scripts/gulpfiles/build_tasks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import rename from 'gulp-rename';
import replace from 'gulp-replace';
import sourcemaps from 'gulp-sourcemaps';

import {execSync} from 'child_process';
import * as fs from 'fs';
import * as fsPromises from 'fs/promises';
import * as path from 'path';
Expand All @@ -25,7 +24,6 @@ import {hideBin} from 'yargs/helpers';

import {
BUILD_DIR,
LANG_BUILD_DIR,
RELEASE_DIR,
TSC_OUTPUT_DIR,
} from './config.mjs';
Expand All @@ -39,13 +37,6 @@ const argv = yargs(hideBin(process.argv)).parse();
// Build //
////////////////////////////////////////////////////////////

/**
* Path to the python runtime.
* This will normalize the command across platforms (e.g. python3 on Linux and
* Mac, python on Windows).
*/
const PYTHON = process.platform === 'win32' ? 'python' : 'python3';

/**
* Posix version of TSC_OUTPUT_DIR
*/
Expand Down Expand Up @@ -330,80 +321,6 @@ const JSCOMP_OFF = [
'visibility',
];

/**
* This task regenerates msg/json/en.js and msg/json/qqq.js from
* msg/messages.js.
*/
export function messages(done) {
// Run js_to_json.py
const jsToJsonCmd = `${PYTHON} scripts/i18n/js_to_json.py \
--input_file ${path.join('msg', 'messages.js')} \
--output_dir ${path.join('msg', 'json')} \
--quiet`;
execSync(jsToJsonCmd, {stdio: 'inherit'});

console.log(`
Regenerated several flies in msg/json/. Now run

git diff msg/json/*.json

and check that operation has not overwritten any modifications made to
hints, etc. by the TranslateWiki volunteers. If it has, backport
their changes to msg/messages.js and re-run 'npm run messages'.

Once you are satisfied that any new hints have been backported you may
go ahead and commit the changes, but note that the messages script
will have removed the translator credits - be careful not to commit
this removal!
`);

done();
}

var languages = null;

/**
* Get list of languages to build langfiles and/or shims for, based on .json
* files in msg/json/, skipping certain entries that do not correspond to an
* actual language). Results are cached as this is called from both
* buildLangfiles and buildLangfileShims.
*/
function getLanguages() {
if (!languages) {
const skip = /^(keys|synonyms|qqq|constants)\.json$/;
languages = fs
.readdirSync(path.join('msg', 'json'))
.filter((file) => file.endsWith('json') && !skip.test(file))
.map((file) => file.replace(/\.json$/, ''));
}
return languages;
}

/**
* This task builds Blockly's lang files.
* msg/*.js
*/
function buildLangfiles(done) {
// Create output directory.
fs.mkdirSync(LANG_BUILD_DIR, {recursive: true});

// Run create_messages.py.
const inputFiles = getLanguages().map((lang) =>
path.join('msg', 'json', `${lang}.json`),
);

const createMessagesCmd = `${PYTHON} ./scripts/i18n/create_messages.py \
--source_lang_file ${path.join('msg', 'json', 'en.json')} \
--source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \
--source_constants_file ${path.join('msg', 'json', 'constants.json')} \
--key_file ${path.join('msg', 'json', 'keys.json')} \
--output_dir ${LANG_BUILD_DIR} \
--quiet ${inputFiles.join(' ')}`;
execSync(createMessagesCmd, {stdio: 'inherit'});

done();
}

/**
* Return the path to the generated chunk exporter for the given
* chunk, relative to TSC_OUTPUT_DIR.
Expand Down Expand Up @@ -786,43 +703,6 @@ ${exportedNames.map((name) => ` ${name},`).join('\n')}
await fsPromises.rm(TMP_PACKAGE_JSON);
}

/**
* This task builds the ESM wrappers used by the langfiles "import"
* entrypoints declared in package.json.
*/
async function buildLangfileShims() {
// Create output directory.
fs.mkdirSync(path.join(RELEASE_DIR, 'msg'), {recursive: true});

// Get the names of the exports from the langfile by require()ing
// msg/messages.js and letting it mutate the (global) Blockly.Msg.
// (We have to do it this way because messages.js is a script and
// not a CJS module with exports.)
globalThis.Blockly = {Msg: {}};
await import('../../msg/messages.js');
const exportedNames = Object.keys(globalThis.Blockly.Msg);
delete globalThis.Blockly;

await Promise.all(
getLanguages().map(async (lang) => {
// Write an ESM wrapper that imports the CJS module and re-exports
// its named exports.
const cjsPath = `./${lang}.js`;
const wrapperPath = path.join(RELEASE_DIR, 'msg', `${lang}.mjs`);
const safeLang = lang.replace(/-/g, '_');

await fsPromises.writeFile(
wrapperPath,
`import ${safeLang} from '${cjsPath}';
export const {
${exportedNames.map((name) => ` ${name},`).join('\n')}
} = ${safeLang};
`,
);
}),
);
}

/**
* This task uses Closure Compiler's ADVANCED_OPTIMIZATIONS mode to
* compile together Blockly core, blocks and generators with a simple
Expand Down Expand Up @@ -862,15 +742,13 @@ function compileAdvancedCompilationTest() {
.pipe(gulp.dest('./tests/compile/'));
}

// Main sequence targets. Each should invoke any immediate prerequisite(s).
export const langfiles = gulp.parallel(buildLangfiles, buildLangfileShims);
// function tsc, above
export const minify = gulp.series(
buildChunkExporters,
buildCompiled,
buildShims,
);
export const build = gulp.parallel(minify, langfiles);
export const build = minify;

// Manually-invokable targets, with prerequisites where required.
// function messages, above
Expand Down
104 changes: 104 additions & 0 deletions packages/blockly/scripts/langfiles.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Functions for building language files.
*/

import {execSync} from 'child_process';
import {mkdirSync, readdirSync} from 'fs';
import {writeFile} from 'fs/promises';
import * as path from 'path';

import {LANG_BUILD_DIR, PYTHON, RELEASE_DIR} from './build_constants.mjs';

let languages = null;

/**
* Get list of languages to build langfiles and/or shims for, based on .json
* files in msg/json/, skipping certain entries that do not correspond to an
* actual language). Results are cached as this is called from both
* buildLangfiles and buildLangfileShims.
*/
function getLanguages() {
if (!languages) {
const skip = /^(keys|synonyms|qqq|constants)\.json$/;
languages = readdirSync(path.join('msg', 'json'))
.filter((file) => file.endsWith('json') && !skip.test(file))
.map((file) => file.replace(/\.json$/, ''));
}
return languages;
}

/**
* This task builds Blockly's lang files.
* msg/*.js
*/
function buildLangfiles() {
// Create output directory.
mkdirSync(LANG_BUILD_DIR, {recursive: true});

// Run create_messages.py.
const inputFiles = getLanguages().map((lang) =>
path.join('msg', 'json', `${lang}.json`),
);

const createMessagesCmd = `${PYTHON} ./scripts/i18n/create_messages.py \
--source_lang_file ${path.join('msg', 'json', 'en.json')} \
--source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \
--source_constants_file ${path.join('msg', 'json', 'constants.json')} \
--key_file ${path.join('msg', 'json', 'keys.json')} \
--output_dir ${LANG_BUILD_DIR} \
--quiet ${inputFiles.join(' ')}`;
execSync(createMessagesCmd, {stdio: 'inherit'});
}

/**
* This task builds the ESM wrappers used by the langfiles "import"
* entrypoints declared in package.json.
*/
async function buildLangfileShims() {
// Create output directory.
mkdirSync(path.join(RELEASE_DIR, 'msg'), {recursive: true});

// Get the names of the exports from the langfile by require()ing
// msg/messages.js and letting it mutate the (global) Blockly.Msg.
// (We have to do it this way because messages.js is a script and
// not a CJS module with exports.)
globalThis.Blockly = {Msg: {}};
await import('../msg/messages.js');
const exportedNames = Object.keys(globalThis.Blockly.Msg);
delete globalThis.Blockly;

await Promise.all(
getLanguages().map(async (lang) => {
// Write an ESM wrapper that imports the CJS module and re-exports
// its named exports.
const cjsPath = `./${lang}.js`;
const wrapperPath = path.join(RELEASE_DIR, 'msg', `${lang}.mjs`);
const safeLang = lang.replace(/-/g, '_');

await writeFile(
wrapperPath,
`import ${safeLang} from '${cjsPath}';
export const {
${exportedNames.map((name) => ` ${name},`).join('\n')}
} = ${safeLang};
`,
);
}),
);
}

/**
* Run all of the functions needed to build langfiles.
*/
async function langfiles() {
await buildLangfiles();
await buildLangfileShims();
}

await langfiles();
44 changes: 44 additions & 0 deletions packages/blockly/scripts/messages.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Functions for generating message files
*/

import {execSync} from 'child_process';
import * as path from 'path';

import {PYTHON} from './build_constants.mjs';

/**
* This task regenerates msg/json/en.js and msg/json/qqq.js from
* msg/messages.js.
*/
export function messages() {
// Run js_to_json.py
const jsToJsonCmd = `${PYTHON} scripts/i18n/js_to_json.py \
--input_file ${path.join('msg', 'messages.js')} \
--output_dir ${path.join('msg', 'json')} \
--quiet`;
execSync(jsToJsonCmd, {stdio: 'inherit'});

console.log(`
Regenerated several flies in msg/json/. Now run

git diff msg/json/*.json

and check that operation has not overwritten any modifications made to
hints, etc. by the TranslateWiki volunteers. If it has, backport
their changes to msg/messages.js and re-run 'npm run messages'.

Once you are satisfied that any new hints have been backported you may
go ahead and commit the changes, but note that the messages script
will have removed the translator credits - be careful not to commit
this removal!
`);
}

messages();
Loading