Skip to content

Commit ea28529

Browse files
authored
Merge branch 'main' into release2
2 parents 84943c3 + f54a9b1 commit ea28529

227 files changed

Lines changed: 39038 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ src/**/*-min.js
1212
src/**/*.min.js
1313
src/virtual-server-main.js
1414
src/service-worker.js
15+
src/mdViewer/**
1516

1617
test/perf/*-files/**/*.js
1718
test/spec/*-files/**/*.js

.github/workflows/tranlate-languages.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ jobs:
2121
- name: Check translation errors file
2222
id: check-errors
2323
run: |
24+
ERRORS=""
2425
if [ -f src/nls/errors.txt ]; then
26+
ERRORS="true"
27+
fi
28+
if [ -f src-mdviewer/src/md-nls-autogenerated/errors.txt ]; then
29+
ERRORS="true"
30+
fi
31+
if [ -n "$ERRORS" ]; then
2532
echo "errors_found=true" >> $GITHUB_ENV
2633
else
2734
echo "errors_found=false" >> $GITHUB_ENV
@@ -37,10 +44,13 @@ jobs:
3744
title: '[Translations BOT] Update String translations'
3845
add-paths: |
3946
src/nls/**
47+
src-mdviewer/src/md-nls-autogenerated/**
48+
src-mdviewer/src/locales/**
4049
body: |
4150
Update string translations
4251
- Auto-generated by `tranlate-languages.yml` action
43-
52+
- Includes Phoenix NLS (`src/nls/`) and mdviewer locales (`src-mdviewer/src/locales/`)
53+
4454
Errors:
4555
${{ env.errors_found == 'true' && 'Errors: Some errors were found, please see GitHub Actions logs.' || 'No Translation Errors. All good!' }}
4656
- name: Check outputs

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Thumbs.db
2828
# ignore node_modules inside src
2929
/src/node_modules
3030
/src-node/node_modules
31+
/src-mdviewer/node_modules
32+
33+
# ignore mdViewer build output
34+
/src/mdViewer
3135
/src/JSUtils/node_modules
3236
/src/JSUtils/node/node_modules
3337

@@ -92,6 +96,7 @@ test/thirdparty/jasmine-reporters
9296

9397
# translations
9498
/src/nls/errors.txt
99+
/src-mdviewer/src/md-nls-autogenerated/errors.txt
95100

96101

97102
# ignore everything in the dev extension directory EXCEPT the README

gulpfile.js/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ function makeJSDist() {
145145
"!src/thirdparty/no-minify/**/*", "!src/thirdparty/xterm/**/*",
146146
"!src/LiveDevelopment/BrowserScripts/RemoteFunctions.js",
147147
"!src/extensionsIntegrated/phoenix-pro/onboarding/**/*",
148-
"!src/extensionsIntegrated/phoenix-pro/unit-tests/**/*"])
148+
"!src/extensionsIntegrated/phoenix-pro/unit-tests/**/*",
149+
"!src/mdViewer/**/*"])
149150
.pipe(minify({
150151
ext:{
151152
min:'.js'
@@ -180,7 +181,8 @@ function makeNonMinifyDist() {
180181
"src/thirdparty/xterm/**/*",
181182
"src/LiveDevelopment/BrowserScripts/RemoteFunctions.js",
182183
"src/extensionsIntegrated/phoenix-pro/onboarding/**/*",
183-
"src/extensionsIntegrated/phoenix-pro/unit-tests/**/*"], {base: 'src'})
184+
"src/extensionsIntegrated/phoenix-pro/unit-tests/**/*",
185+
"src/mdViewer/**/*"], {base: 'src'})
184186
.pipe(dest('dist'));
185187
}
186188

@@ -428,6 +430,7 @@ function releaseProd() {
428430
function translateStrings() {
429431
return new Promise(async (resolve)=>{ // eslint-disable-line
430432
await Translate.translate();
433+
await Translate.translateMdviewer();
431434
resolve();
432435
});
433436
}
@@ -706,7 +709,7 @@ function _makeBracketsConcatJSInternal(isDevBuild = true) {
706709
`${srcDir}preferences/PreferencesImpl.js` // tests does require magic on prefs, so exclude
707710
];
708711
const pathsToMerge = [];
709-
const PathsToIgnore = ["assets", "thirdparty", "extensions"];
712+
const PathsToIgnore = ["assets", "thirdparty", "extensions", "mdViewer"];
710713
for(let dir of fs.readdirSync(srcDir, {withFileTypes: true})){
711714
if(dir.isDirectory() && !PathsToIgnore.includes(dir.name)){
712715
pathsToMerge.push(dir.name);

gulpfile.js/translateStrings.js

Lines changed: 159 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
/* eslint-env node */
2222
const fs = require('fs');
23+
const path = require('path');
2324
const CORE_AI_TRANSLATE_API_KEY = process.env.CORE_AI_TRANSLATE_API_KEY;
2425

2526
// A global accumulator object initialized to zero
@@ -83,7 +84,7 @@ function aggregateUtilizationMetrics(obj) {
8384
const translationContext =
8485
`This is a bunch of strings extracted from a JavaScript file used to develop our product with is a text editor.
8586
Some strings may have HTML or templates(mustache library used).
86-
The brand name Phoenix Pro must remain in English and should never be translated.
87+
The brand name "Phoenix Pro" must remain in English and should never be translated.
8788
Please translate these strings accurately.
8889
`;
8990

@@ -131,11 +132,11 @@ async function getTranslation(apiInput) {
131132
}
132133
}
133134

134-
function _getAllNLSFolders() {
135-
let names = fs.readdirSync('src/nls');
135+
function _getAllNLSFolders(nlsDir) {
136+
let names = fs.readdirSync(nlsDir);
136137
let nlsFolders =[];
137138
for(let name of names){
138-
let stat = fs.statSync(`src/nls/${name}`);
139+
let stat = fs.statSync(path.join(nlsDir, name));
139140
if(stat.isDirectory()){
140141
nlsFolders.push(name);
141142
}
@@ -180,18 +181,18 @@ const FILE_HEADER = `/*
180181
*/
181182
182183
define(`,
183-
FILE_FOOTER = ');';
184+
FILE_FOOTER = ');';
184185

185-
function _isTranslatableKey(key) {
186+
function _isTranslatableKey(key, sourceStrings) {
186187
const doNotTranslateDirective = '_DO_NOT_TRANSLATE';
187188
const translationDisabledForKey = `${key}${doNotTranslateDirective}`;
188-
if(key.endsWith(doNotTranslateDirective) || rootStrings[translationDisabledForKey] === 'true'){
189+
if(key.endsWith(doNotTranslateDirective) || sourceStrings[translationDisabledForKey] === 'true'){
189190
return false;
190191
}
191192
return true;
192193
}
193194

194-
async function coreAiTranslate(stringsToTranslate, lang) {
195+
async function coreAiTranslate(stringsToTranslate, lang, errorsFile) {
195196
if(!Object.keys(stringsToTranslate).length){
196197
return {};
197198
}
@@ -203,7 +204,7 @@ async function coreAiTranslate(stringsToTranslate, lang) {
203204
if(translations.failedLanguages.length){
204205
const errorStr = `Error translating ${lang}. it has failures `;
205206
console.error(errorStr);
206-
fs.writeFileSync(`src/nls/errors.txt`, errorStr);
207+
fs.writeFileSync(errorsFile, errorStr);
207208
// this is oke to continue in case of partial translations.
208209
}
209210
let translationForLanguage = translations.translations[lang];
@@ -214,7 +215,7 @@ async function coreAiTranslate(stringsToTranslate, lang) {
214215
if(!translationForLanguage){
215216
const errorStr = `Error translating. AI response doesnt have the language ${lang} translated!`;
216217
console.error(errorStr);
217-
fs.writeFileSync(`src/nls/errors.txt`, errorStr);
218+
fs.writeFileSync(errorsFile, errorStr);
218219
return {};
219220
}
220221
return translationForLanguage;
@@ -272,24 +273,35 @@ function getSortedObject(obj) {
272273
*
273274
* Finally, we update all the autogenerated translations to disk.
274275
*
275-
* @param lang
276+
* @param {string} lang - locale code
277+
* @param {object} config - { nlsDir, sourceStrings, format, errorsFile }
276278
* @return {Promise<void>}
277279
* @private
278280
*/
279-
async function _processLang(lang) {
281+
async function _processLang(lang, config) {
280282
if(lang === 'root'){
281283
return;
282284
}
283-
const expertTranslations = _getJson(`src/nls/${lang}/expertTranslations.json`, 'utf8');
284-
let lastTranslated = _getJson(`src/nls/${lang}/lastTranslated.json`, 'utf8');
285-
require(`../src/nls/${lang}/strings`);
286-
let localeStringsJS = requireDefinedStrings;
285+
const { nlsDir, sourceStrings, format, errorsFile } = config;
286+
const langDir = path.join(nlsDir, lang);
287+
288+
const expertTranslations = _getJson(path.join(langDir, 'expertTranslations.json'));
289+
let lastTranslated = _getJson(path.join(langDir, 'lastTranslated.json'));
290+
291+
let localeStringsJS;
292+
if (format === 'json') {
293+
localeStringsJS = _getJson(path.join(langDir, 'strings.json'));
294+
} else {
295+
require(path.resolve(langDir, 'strings'));
296+
localeStringsJS = requireDefinedStrings;
297+
}
298+
287299
let translations = {}, updatedLastTranslatedJSON={}, pendingTranslate = {};
288-
for(let rootKey of Object.keys(rootStrings)){
289-
if(!_isTranslatableKey(rootKey)){
300+
for(let rootKey of Object.keys(sourceStrings)){
301+
if(!_isTranslatableKey(rootKey, sourceStrings)){
290302
continue; // move on to next string
291303
}
292-
let englishStringToTranslate = rootStrings[rootKey];
304+
let englishStringToTranslate = sourceStrings[rootKey];
293305
let lastTranslatedEnglishString = lastTranslated[rootKey];
294306
if(englishStringToTranslate === lastTranslatedEnglishString){
295307
// we have already translated this in the last pass.
@@ -316,54 +328,169 @@ async function _processLang(lang) {
316328
}
317329
//let translatedText = await _translateString(englishStringToTranslate, lang);
318330
console.log(`Translating ${Object.keys(pendingTranslate).length} strings to`, lang);
319-
const aiTranslations = await coreAiTranslate(pendingTranslate, lang);
320-
const allRootKeys = new Set(Object.keys(rootStrings));
331+
const aiTranslations = await coreAiTranslate(pendingTranslate, lang, errorsFile);
332+
const allRootKeys = new Set(Object.keys(sourceStrings));
321333
for(let rootKey of Object.keys(pendingTranslate)){
322334
if(!allRootKeys.has(rootKey)){
323335
// AI hallucinated a root key?
324336
const errorStr = `AI translated for a root key that doesnt exist!!! in ${lang}: ${rootKey} \nTranslation: ${aiTranslations[rootKey]}`;
325337
console.error(errorStr);
326-
fs.writeFileSync(`src/nls/errors.txt`, errorStr);
338+
fs.writeFileSync(errorsFile, errorStr);
327339
continue;
328340
}
329-
let englishStringToTranslate = rootStrings[rootKey];
341+
let englishStringToTranslate = sourceStrings[rootKey];
330342
const translatedText = aiTranslations[rootKey];
331343
if(translatedText){
332344
translations[rootKey] = translatedText;
333345
updatedLastTranslatedJSON[rootKey] = englishStringToTranslate;
334346
}
335347
}
336348
// now detect any keys that has not yet been translated
337-
const allKeys = Object.keys(rootStrings).filter(_isTranslatableKey);
349+
const allKeys = Object.keys(sourceStrings).filter(k => _isTranslatableKey(k, sourceStrings));
338350
const translatedKeys = Object.keys(translations);
339351
const notTranslated = allKeys.filter(key => !translatedKeys.includes(key));
340352
if(notTranslated.length){
341353
const errorStr = `Some strings not translated in ${lang}\n${notTranslated}`;
342354
console.error(errorStr);
343-
fs.writeFileSync(`src/nls/errors.txt`, errorStr);
355+
fs.writeFileSync(errorsFile, errorStr);
344356
}
345357

346-
let translatedStringsJSON = JSON.stringify(translations, null, 2);
347-
let fileToWrite = `${FILE_HEADER}${translatedStringsJSON}${FILE_FOOTER}`;
348-
if(!shallowEqual(translations, localeStringsJS)){
349-
fs.writeFileSync(`src/nls/${lang}/strings.js`, fileToWrite);
358+
if (format === 'json') {
359+
// Write plain JSON
360+
if(!shallowEqual(translations, localeStringsJS)){
361+
fs.writeFileSync(path.join(langDir, 'strings.json'),
362+
JSON.stringify(translations, null, 2));
363+
}
364+
} else {
365+
// Write define()-wrapped JS
366+
let translatedStringsJSON = JSON.stringify(translations, null, 2);
367+
let fileToWrite = `${FILE_HEADER}${translatedStringsJSON}${FILE_FOOTER}`;
368+
if(!shallowEqual(translations, localeStringsJS)){
369+
fs.writeFileSync(path.join(langDir, 'strings.js'), fileToWrite);
370+
}
350371
}
351372
if(!shallowEqual(updatedLastTranslatedJSON, lastTranslated)){
352373
const sortedList = getSortedObject(updatedLastTranslatedJSON);
353-
fs.writeFileSync(`src/nls/${lang}/lastTranslated.json`, JSON.stringify(sortedList, null, 2));
374+
fs.writeFileSync(path.join(langDir, 'lastTranslated.json'),
375+
JSON.stringify(sortedList, null, 2));
354376
}
355377
}
356378

379+
// ---- Phoenix NLS translation ----
380+
357381
async function translate() {
358382
console.log("please make sure that core.ai lang translation service credentials are available as env vars.");
359383
return new Promise(async (resolve)=>{
360-
let langs = _getAllNLSFolders();
384+
const nlsDir = 'src/nls';
385+
let langs = _getAllNLSFolders(nlsDir);
361386
console.log(langs);
387+
const config = {
388+
nlsDir,
389+
sourceStrings: rootStrings,
390+
format: 'js',
391+
errorsFile: 'src/nls/errors.txt'
392+
};
362393
for(let lang of langs){
363-
await _processLang(lang);
394+
await _processLang(lang, config);
364395
}
365396
resolve();
366397
});
367398
}
368399

400+
// ---- mdviewer translation ----
401+
402+
/**
403+
* Flatten a nested object into dot-notation keys.
404+
* { toolbar: { done: "Done" } } → { "toolbar.done": "Done" }
405+
*/
406+
function _flattenObject(obj, prefix = '') {
407+
const result = {};
408+
for (const key of Object.keys(obj)) {
409+
const fullKey = prefix ? `${prefix}.${key}` : key;
410+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
411+
Object.assign(result, _flattenObject(obj[key], fullKey));
412+
} else {
413+
result[fullKey] = obj[key];
414+
}
415+
}
416+
return result;
417+
}
418+
419+
/**
420+
* Unflatten dot-notation keys back into a nested object.
421+
* { "toolbar.done": "Done" } → { toolbar: { done: "Done" } }
422+
*/
423+
function _unflattenObject(obj) {
424+
const result = {};
425+
for (const flatKey of Object.keys(obj)) {
426+
const parts = flatKey.split('.');
427+
let current = result;
428+
for (let i = 0; i < parts.length - 1; i++) {
429+
if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
430+
current[parts[i]] = {};
431+
}
432+
current = current[parts[i]];
433+
}
434+
current[parts[parts.length - 1]] = obj[flatKey];
435+
}
436+
return result;
437+
}
438+
439+
async function translateMdviewer() {
440+
const mdNlsDir = 'src-mdviewer/src/md-nls-autogenerated';
441+
const localesDir = 'src-mdviewer/src/locales';
442+
const enJsonPath = path.join(localesDir, 'en.json');
443+
const rootStringsPath = path.join(mdNlsDir, 'root', 'strings.json');
444+
445+
if (!fs.existsSync(enJsonPath)) {
446+
console.log("[mdviewer] en.json not found, skipping mdviewer translation.");
447+
return;
448+
}
449+
450+
// Flatten en.json → root/strings.json (source of truth → NLS format)
451+
const enNested = _getJson(enJsonPath);
452+
const enFlat = getSortedObject(_flattenObject(enNested));
453+
if (!fs.existsSync(path.join(mdNlsDir, 'root'))) {
454+
fs.mkdirSync(path.join(mdNlsDir, 'root'), { recursive: true });
455+
}
456+
fs.writeFileSync(rootStringsPath, JSON.stringify(enFlat, null, 2));
457+
console.log(`[mdviewer] Flattened en.json → root/strings.json (${Object.keys(enFlat).length} keys)`);
458+
459+
const mdRootStrings = enFlat;
460+
461+
console.log("[mdviewer] Starting mdviewer locale translation...");
462+
let langs = _getAllNLSFolders(mdNlsDir);
463+
console.log("[mdviewer] Locales:", langs);
464+
465+
const config = {
466+
nlsDir: mdNlsDir,
467+
sourceStrings: mdRootStrings,
468+
format: 'json',
469+
errorsFile: path.join(mdNlsDir, 'errors.txt')
470+
};
471+
472+
for (let lang of langs) {
473+
await _processLang(lang, config);
474+
}
475+
476+
// Copy translated flat JSON → nested locale files for mdviewer runtime
477+
console.log("[mdviewer] Copying translations to locales folder...");
478+
for (let lang of langs) {
479+
if (lang === 'root') continue;
480+
const flatStrings = _getJson(path.join(mdNlsDir, lang, 'strings.json'));
481+
if (!Object.keys(flatStrings).length) continue;
482+
const nested = _unflattenObject(flatStrings);
483+
fs.writeFileSync(
484+
path.join(localesDir, `${lang}.json`),
485+
JSON.stringify(nested, null, 2) + '\n'
486+
);
487+
}
488+
489+
console.log("[mdviewer] mdviewer locale translation complete.");
490+
}
491+
369492
exports.translate = translate;
493+
exports.translateMdviewer = translateMdviewer;
494+
exports.coreAiTranslate = coreAiTranslate;
495+
exports.shallowEqual = shallowEqual;
496+
exports.getSortedObject = getSortedObject;

0 commit comments

Comments
 (0)