diff --git a/lib/editor/tiny/lang/en/editor_tiny.php b/lib/editor/tiny/lang/en/editor_tiny.php
index 0767a54a025..723035aa213 100644
--- a/lib/editor/tiny/lang/en/editor_tiny.php
+++ b/lib/editor/tiny/lang/en/editor_tiny.php
@@ -183,7 +183,7 @@ $string['tiny:horizontal_align'] = 'Horizontal align';
$string['tiny:horizontal_line'] = 'Horizontal line';
$string['tiny:horizontal_space'] = 'Horizontal space';
$string['tiny:id'] = 'ID';
-$string['tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_underscores.'] = 'ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.';
+$string['tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_'] = 'ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.';
$string['tiny:image_is_decorative'] = 'Image is decorative';
$string['tiny:image_list'] = 'Image list';
$string['tiny:image_title'] = 'Image title';
@@ -274,7 +274,7 @@ $string['tiny:paste'] = 'Paste';
$string['tiny:paste_as_text'] = 'Paste as text';
$string['tiny:paste_column_after'] = 'Paste column after';
$string['tiny:paste_column_before'] = 'Paste column before';
-$string['tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_toggle_this_option_off.'] = 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.';
+$string['tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_'] = 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.';
$string['tiny:paste_or_type_a_link'] = 'Paste or type a link';
$string['tiny:paste_row_after'] = 'Paste row after';
$string['tiny:paste_row_before'] = 'Paste row before';
@@ -308,7 +308,7 @@ $string['tiny:resize'] = 'Resize';
$string['tiny:restore_last_draft'] = 'Restore last draft';
$string['tiny:rich_text_area'] = 'Rich Text Area';
$string['tiny:rich_text_area._press_alt-0_for_help.'] = 'Rich Text Area. Press ALT-0 for help.';
-$string['tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_help'] = 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help';
+$string['tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_hel'] = 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help';
$string['tiny:ridge'] = 'Ridge';
$string['tiny:right'] = 'Right';
$string['tiny:right_to_left'] = 'Right to left';
@@ -355,9 +355,9 @@ $string['tiny:templates'] = 'Templates';
$string['tiny:text'] = 'Text';
$string['tiny:text_color'] = 'Text color';
$string['tiny:text_to_display'] = 'Text to display';
-$string['tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mailto_prefix'] = 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?';
-$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_http_prefix'] = 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?';
-$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_https_prefix'] = 'The URL you entered seems to be an external link. Do you want to add the required https:// prefix?';
+$string['tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mai'] = 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?';
+$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_htt'] = 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?';
+$string['tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_ht1'] = 'The URL you entered seems to be an external link. Do you want to add the required https:// prefix?';
$string['tiny:title'] = 'Title';
$string['tiny:to_open_the_popup_press_shiftenter'] = 'To open the popup, press Shift+Enter';
$string['tiny:tools'] = 'Tools';
@@ -388,7 +388,7 @@ $string['tiny:yellow'] = 'Yellow';
$string['tiny:yes'] = 'Yes';
$string['tiny:you_are_using_0'] = 'You are using {0}';
$string['tiny:you_have_unsaved_changes_are_you_sure_you_want_to_navigate_away'] = 'You have unsaved changes are you sure you want to navigate away?';
-$string['tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_keyboard_shortcuts_instead.'] = 'Your browser doesn\'t support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.';
+$string['tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_ke'] = 'Your browser doesn\'t support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.';
$string['tiny:alignment1'] = 'alignment';
$string['tiny:austral_sign'] = 'austral sign';
$string['tiny:cedi_sign'] = 'cedi sign';
diff --git a/lib/editor/tiny/readme_moodle.md b/lib/editor/tiny/readme_moodle.md
index 07dff6205f5..72982c89cc5 100644
--- a/lib/editor/tiny/readme_moodle.md
+++ b/lib/editor/tiny/readme_moodle.md
@@ -43,23 +43,23 @@
## Update procedure for included TinyMCE translations
-1. Visit https://www.tiny.cloud/get-tiny/language-packages/ and download a translation which has been fully translated, for example the German translation.
-2. If you did not download the German translation, update the final line of `tools/getOriginals.mjs` to the language code for the relevant translation.
+1. Visit https://www.tiny.cloud/get-tiny/language-packages/ and download the "TinyMCE 6 All languages" zip file.
+2. Check the list of languages and confirm that the German translation is still at 100%. If not, then make a note of a language which is.
3. Unzip the translation into a new directory:
- ```
+ ```bash
langdir=`mktemp -d`
cd "${langdir}"
- unzip path/to/de.zip
+ unzip path/to/langs.zip
```
4. Run the translation tool:
- ```
- node "${MOODLEDIR}/tools/getOriginals.mjs"
+ ```bash
+ node "${MOODLEDIR}/tools/createLangStrings.mjs"
```
- This will generate two files
+ This will generate a language file for each available Language, as well as a `tinystrings.json`, and a `strings.php` which will be used in the subsequent steps.
5. Copy the `tinystrings.json` file into the Moodle directory
@@ -75,9 +75,12 @@
```
7. Commit changes
+8. If required, the remaining language strings can be fed into AMOS.
---
+**Note:** A set of language files are also generated for all supported translations and may be submitted to AMOS if desired.
+
**Note:** You will need to manually check for any Moodle-updated language strings as part of this change (for example any from the en_fixes).
---
diff --git a/lib/editor/tiny/tinystrings.json b/lib/editor/tiny/tinystrings.json
index 88b54fac2f5..c0ca466ac6f 100644
--- a/lib/editor/tiny/tinystrings.json
+++ b/lib/editor/tiny/tinystrings.json
@@ -154,7 +154,7 @@
"tiny:horizontal_line": "Horizontal line",
"tiny:horizontal_space": "Horizontal space",
"tiny:id": "ID",
- "tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.",
+ "tiny:id_should_start_with_a_letter_followed_only_by_letters_numbers_dashes_dots_colons_or_": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.",
"tiny:image_is_decorative": "Image is decorative",
"tiny:image_list": "Image list",
"tiny:image_title": "Image title",
@@ -245,7 +245,7 @@
"tiny:paste_as_text": "Paste as text",
"tiny:paste_column_after": "Paste column after",
"tiny:paste_column_before": "Paste column before",
- "tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_toggle_this_option_off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.",
+ "tiny:paste_is_now_in_plain_text_mode._contents_will_now_be_pasted_as_plain_text_until_you_": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.",
"tiny:paste_or_type_a_link": "Paste or type a link",
"tiny:paste_row_after": "Paste row after",
"tiny:paste_row_before": "Paste row before",
@@ -279,7 +279,7 @@
"tiny:restore_last_draft": "Restore last draft",
"tiny:rich_text_area": "Rich Text Area",
"tiny:rich_text_area._press_alt-0_for_help.": "Rich Text Area. Press ALT-0 for help.",
- "tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help",
+ "tiny:rich_text_area._press_alt-f9_for_menu._press_alt-f10_for_toolbar._press_alt-0_for_hel": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help",
"tiny:ridge": "Ridge",
"tiny:right": "Right",
"tiny:right_to_left": "Right to left",
@@ -326,9 +326,9 @@
"tiny:text": "Text",
"tiny:text_color": "Text color",
"tiny:text_to_display": "Text to display",
- "tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mailto_prefix": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",
- "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_http_prefix": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",
- "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_https_prefix": "The URL you entered seems to be an external link. Do you want to add the required https:// prefix?",
+ "tiny:the_url_you_entered_seems_to_be_an_email_address._do_you_want_to_add_the_required_mai": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",
+ "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_htt": "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",
+ "tiny:the_url_you_entered_seems_to_be_an_external_link._do_you_want_to_add_the_required_ht1": "The URL you entered seems to be an external link. Do you want to add the required https:// prefix?",
"tiny:title": "Title",
"tiny:to_open_the_popup_press_shiftenter": "To open the popup, press Shift+Enter",
"tiny:tools": "Tools",
@@ -359,7 +359,7 @@
"tiny:yes": "Yes",
"tiny:you_are_using_0": "You are using {0}",
"tiny:you_have_unsaved_changes_are_you_sure_you_want_to_navigate_away": "You have unsaved changes are you sure you want to navigate away?",
- "tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_keyboard_shortcuts_instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.",
+ "tiny:your_browser_doesnt_support_direct_access_to_the_clipboard._please_use_the_ctrlxcv_ke": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.",
"tiny:alignment1": "alignment",
"tiny:austral_sign": "austral sign",
"tiny:cedi_sign": "cedi sign",
diff --git a/lib/editor/tiny/tools/createLangStrings.mjs b/lib/editor/tiny/tools/createLangStrings.mjs
new file mode 100644
index 00000000000..d0f747dd077
--- /dev/null
+++ b/lib/editor/tiny/tools/createLangStrings.mjs
@@ -0,0 +1,139 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+import {readdir, readFile, writeFile, mkdir} from 'fs/promises';
+import {join as joinPath} from 'path';
+
+const fullyTranslatedLanguage = 'de';
+const maxStringIdentifierLength = 90;
+
+const readStringsFromLanguages = async (language) => {
+ const fileContent = await readFile(`./langs/${language}.js`, 'utf-8');
+
+ let translations = {};
+ const tinymce = {
+ addI18n: (language, strings) => {
+ translations = strings;
+ },
+ };
+
+ eval(fileContent);
+
+ return Object.keys(translations).sort().reduce((sortedTranslations, key) => {
+ sortedTranslations[key] = translations[key];
+ return sortedTranslations;
+ }, {});
+};
+
+const getStringMap = (strings) => {
+ const stringMap = {};
+
+ const getUniqueKeyForString = (string, modifier = 0) => {
+ let stringKey = string.toLowerCase()
+ .replaceAll(' ', '_')
+ .replaceAll(/\{(\d)\}/g, '$1')
+ .replaceAll('#', 'hash')
+ .replaceAll(/[^a-z0-9_\-\.]/g, '')
+ ;
+
+ if (stringKey === '') {
+ throw new Error(`The calculated key for '${string}' was empty`);
+ }
+
+ stringKey = `tiny:${stringKey}`;
+
+ if (stringKey.length > maxStringIdentifierLength) {
+ const modifierLength = modifier === 0 ? 0 : `${modifier}`.length;
+ stringKey = stringKey.slice(0, maxStringIdentifierLength - modifierLength);
+ }
+
+ if (modifier > 0) {
+ stringKey = `${stringKey}${modifier}`;
+ }
+
+ if (typeof stringMap[stringKey] !== 'undefined') {
+ return getUniqueKeyForString(string, ++modifier);
+ }
+
+ return stringKey;
+ };
+
+ strings.forEach((string) => {
+ const stringKey = getUniqueKeyForString(string);
+ if (typeof stringMap[stringKey] !== 'undefined') {
+ throw new Error(`Found existing key ${stringKey}`);
+ }
+
+ stringMap[stringKey] = string;
+ });
+
+ return stringMap;
+};
+
+const getPhpStrings = (stringMap, translatedStrings) => Object.entries(stringMap).map(([stringKey, englishString]) => {
+ if (translatedStrings[englishString].length === 0) {
+ return null;
+ }
+ return `$string['${stringKey}'] = '${translatedStrings[englishString].replaceAll("'", "\\\'")}';`
+})
+.filter((value) => value !== null)
+.join("\n");
+
+const storeEnglishStrings = async(stringMap) => {
+ const englishStrings = Object.entries(stringMap).map(([stringKey, stringValue]) => {
+ return `$string['${stringKey}'] = '${stringValue.replace("'", "\\\'")}';`
+ }).join("\n");
+ await writeFile('./strings.php', englishStrings + "\n");
+ await writeFile('./tinystrings.json', JSON.stringify(stringMap, null, ' '));
+}
+
+const constructTranslationFile = async(language, englishStringMap = null) => {
+ const strings = await readStringsFromLanguages(language);
+ console.log(`Generating translation data for ${language} with ${Object.keys(strings).length} strings`);
+ const stringMap = englishStringMap === null ? getStringMap(Object.keys(strings)) : englishStringMap;
+
+ const langDir = joinPath('lang', language);
+ await mkdir(langDir, {recursive: true});
+
+ const fileContent = ` {
+ const {stringMap} = await constructTranslationFile(fullyTranslatedLanguage);
+ storeEnglishStrings(stringMap);
+
+ readdir('./langs/').then((files) => {
+ files.forEach(async(file) => {
+ const langIdent = file.replace('.js', '');
+ if (langIdent === fullyTranslatedLanguage) {
+ // This language is already done.
+ return;
+ }
+ await constructTranslationFile(langIdent, stringMap);
+ });
+ });
+}
+
+constructTranslationFiles();
diff --git a/lib/editor/tiny/tools/getOriginals.mjs b/lib/editor/tiny/tools/getOriginals.mjs
deleted file mode 100644
index e2a028b5a8c..00000000000
--- a/lib/editor/tiny/tools/getOriginals.mjs
+++ /dev/null
@@ -1,86 +0,0 @@
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle. If not, see .
-
-import {readFile, writeFile} from 'fs/promises';
-
-const readStringsFromLanguages = async (language) => {
- const fileContent = await readFile(`./langs/${language}.js`, 'utf-8');
-
- const translations = [];
- const tinymce = {
- addI18n: (language, strings) => {
- translations.push(...(Object.keys(strings)));
- },
- };
-
- eval(fileContent);
-
- return translations.sort();
-};
-
-const getStringMap = (strings) => {
- const stringMap = {};
-
- const getUniqueKeyForString = (string, modifier = 0) => {
- let stringKey = string.toLowerCase()
- .replaceAll(' ', '_')
- .replaceAll(/\{(\d)\}/g, '$1')
- .replaceAll('#', 'hash')
- .replaceAll(/[^a-z0-9_\-\.]/g, '')
- ;
-
- if (stringKey === '') {
- throw new Error(`The calculated key for '${string}' was empty`);
- }
-
- stringKey = `tiny:${stringKey}`;
-
- if (modifier > 0) {
- stringKey = `${stringKey}${modifier}`;
- }
-
- if (typeof stringMap[stringKey] !== 'undefined') {
- return getUniqueKeyForString(string, ++modifier);
- }
-
- return stringKey;
- };
-
- strings.forEach((string) => {
- const stringKey = getUniqueKeyForString(string);
- if (typeof stringMap[stringKey] !== 'undefined') {
- throw new Error(`Found existing key ${stringKey}`);
- }
-
- stringMap[stringKey] = string;
- });
-
- return stringMap;
-};
-
-const getPhpStrings = (stringMap) => Object.entries(stringMap).map(([stringKey, stringValue]) => {
- return `$string['${stringKey}'] = '${stringValue.replace("'", "\\\'")}';`
-}).join("\n");
-
-
-const constructTranslationFile = async(language) => {
- const strings = await readStringsFromLanguages(language);
- const stringMap = getStringMap(strings);
-
- await writeFile('./strings.php', getPhpStrings(stringMap) + "\n");
- await writeFile('./tinystrings.json', JSON.stringify(stringMap, null, ' '));
-};
-
-constructTranslationFile('de');