MDL-75246 editor_tiny: Add initial translation support

Part of MDL-75966

Add support for translation of the TinyMCE interface.

TinyMCE translates English language strings rather than keys, and a tool
to perform this translation is included.
This commit is contained in:
Andrew Nicols 2022-07-19 16:35:03 +08:00
parent 7e5c81e5a5
commit d8cf77a127
8 changed files with 1298 additions and 2 deletions

View File

@ -1,3 +1,3 @@
define("editor_tiny/editor",["exports","./loader","core/pending"],(function(_exports,_loader,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};const getPlugins=options=>options.plugins?options.plugins:defaultOptions.plugins?defaultOptions.plugins:{},getStandardConfig=(target,tinyMCE,options,plugins)=>({target:target,language:document.querySelector("html").lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,toolbar_mode:"sliding",toolbar:[{name:"history",items:["undo","redo"]},{name:"styles",items:["styles"]},{name:"formatting",items:["bold","italic"]},{name:"alignment",items:["alignleft","aligncenter","alignright","alignjustify"]},{name:"indentation",items:["outdent","indent"]},{name:"comments",items:["addcomment"]}],menu:{},plugins:[...plugins],skin:"oxide",promotion:!1}),setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]),{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,options,pluginNames);pluginConfig.forEach((pluginConfig=>{"function"==typeof pluginConfig.configure&&Object.assign(instanceConfig,pluginConfig.configure(instanceConfig))}));const[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),editor.moodleOptions=options,pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}}));
define("editor_tiny/editor",["exports","./loader","core/pending"],(function(_exports,_loader,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};(async()=>{const lang=document.querySelector("html").lang,[tinyMCE,langData]=await Promise.all([(0,_loader.getTinyMCE)(),(language=lang,fetch("".concat(M.cfg.wwwroot,"/lib/editor/tiny/lang.php/").concat(M.cfg.langrev,"/").concat(language)).then((response=>response.json())))]);var language;tinyMCE.addI18n(lang,langData)})();const getPlugins=options=>options.plugins?options.plugins:defaultOptions.plugins?defaultOptions.plugins:{},getStandardConfig=(target,tinyMCE,options,plugins)=>({target:target,language:document.querySelector("html").lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,toolbar_mode:"sliding",toolbar:[{name:"history",items:["undo","redo"]},{name:"styles",items:["styles"]},{name:"formatting",items:["bold","italic"]},{name:"alignment",items:["alignleft","aligncenter","alignright","alignjustify"]},{name:"indentation",items:["outdent","indent"]},{name:"comments",items:["addcomment"]}],menu:{},plugins:[...plugins],skin:"oxide",promotion:!1}),setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]),{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,options,pluginNames);pluginConfig.forEach((pluginConfig=>{"function"==typeof pluginConfig.configure&&Object.assign(instanceConfig,pluginConfig.configure(instanceConfig))}));const[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),editor.moodleOptions=options,pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}}));
//# sourceMappingURL=editor.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -76,6 +76,10 @@ const importPluginList = async(pluginList) => {
};
};
const fetchLanguage = (language) => fetch(
`${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}`
).then(response => response.json());
export const getAllInstances = () => new Map(instanceMap.entries());
/**
@ -114,6 +118,14 @@ export const setupForElementId = ({elementId, options}) => {
return setupForTarget(target, options);
};
const initialisePage = async() => {
const lang = document.querySelector('html').lang;
const [tinyMCE, langData] = await Promise.all([getTinyMCE(), fetchLanguage(lang)]);
tinyMCE.addI18n(lang, langData);
};
initialisePage();
const getPlugins = (options) => {
if (options.plugins) {
return options.plugins;

347
lib/editor/tiny/lang.php Normal file
View File

@ -0,0 +1,347 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Tiny text editor integration - Language Producer.
*
* @package editor_tiny
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace editor_tiny;
// Disable moodle specific debug messages and any errors in output,
// comment out when debugging or better look into error log!
define('NO_DEBUG_DISPLAY', true);
// We need just the values from config.php and minlib.php.
define('ABORT_AFTER_CONFIG', true);
// This stops immediately at the beginning of lib/setup.php.
require('../../../config.php');
/**
* An anonymous class to handle loading and serving lang files for TinyMCE.
*
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lang {
/** @var string The language code to load */
protected $lang;
/** @var int The revision requested */
protected $rev;
/** @var bool Whether Moodle is fully loaded or not */
protected $fullyloaded = false;
/**
* Constructor to load and serve the langfile.
*/
public function __construct() {
$this->parse_file_information_from_url();
$this->serve_file();
}
/**
* Parse the file information from the URL.
*/
protected function parse_file_information_from_url(): void {
global $CFG;
// The URL format is /[revision]/[lang].
// The revision is an integer with negative values meaning the file is not cached.
// The lang is a simple word with no directory separators or special characters.
if ($slashargument = min_get_slash_argument()) {
$slashargument = ltrim($slashargument, '/');
if (substr_count($slashargument, '/') < 1) {
css_send_css_not_found();
}
[$rev, $lang] = explode('/', $slashargument, 2);
$rev = min_clean_param($rev, 'RAW');
$lang = min_clean_param($lang, 'SAFEDIR');
} else {
$rev = min_optional_param('rev', 0, 'RAW');
$lang = min_optional_param('lang', 'standard', 'SAFEDIR');
}
$this->lang = $lang;
$this->rev = $rev;
$this->candidatefile = "{$CFG->localcachedir}/editor_tiny/{$this->rev}/lang/{$this->lang}/lang.json";
}
/**
* Serve the language pack content.
*/
protected function serve_file(): void {
// Attempt to send the cached langpack.
if ($this->rev > 0) {
if ($this->is_candidate_file_available()) {
// The send_cached_file_if_available function will exit if successful.
// In theory the file could become unavailable after checking that the file exists.
// Whilst this is unlikely, fall back to caching the content below.
$this->send_cached_pack();
}
// The file isn't cached yet.
// Load the content. store it in the cache, and serve it.
$strings = $this->load_language_pack();
$this->store_lang_file($strings);
$this->send_cached();
} else {
// If the revision is less than 0, then do not cache anything.
$strings = $this->load_language_pack();
$this->send_uncached($strings);
}
}
/**
* Load the full Moodle Framework.
*/
protected function load_full_moodle(): void {
global $CFG, $DB, $SESSION, $OUTPUT, $PAGE;
if ($this->is_full_moodle_loaded()) {
return;
}
// Ok, now we need to start normal moodle script, we need to load all libs and $DB.
define('ABORT_AFTER_CONFIG_CANCEL', true);
// Session not used here.
define('NO_MOODLE_COOKIES', true);
// Ignore upgrade check.
define('NO_UPGRADE_CHECK', true);
require("{$CFG->dirroot}/lib/setup.php");
$this->fullyloaded = true;
}
/**
* Check whether Moodle is fully loaded.
*
* @return bool
*/
public function is_full_moodle_loaded(): bool {
return $this->fullyloaded;
}
/**
* Load the language pack strings.
*
* @return string[]
*/
protected function load_language_pack(): array {
// We need to load the full moodle API to use the string manager.
$this->load_full_moodle();
// We maintain a list of string identifier to original TinyMCE string.
// TinyMCE uses English language strings to perform translations.
$stringlist = file_get_contents(__DIR__ . "/tinystrings.json");
if (empty($stringlist)) {
$this->send_not_found("Failed to load strings from tinystrings.json");
}
$stringlist = json_decode($stringlist, true);
if (empty($stringlist)) {
$this->send_not_found("Failed to load strings from tinystrings.json");
}
// Load all strings for the TinyMCE Editor which have a prefix of `tiny:` from the Moodle String Manager.
$stringmanager = get_string_manager();
$translatedvalues = array_filter(
$stringmanager->load_component_strings('editor_tiny', $this->lang),
function(string $value, string $key): bool {
return strpos($key, 'tiny:') === 0;
},
ARRAY_FILTER_USE_BOTH
);
// We will associate the _original_ TinyMCE string to its translation, but only where it is different.
// Where the original TinyMCE string matches the Moodle translation of it, we do not supply the string.
$strings = [];
foreach ($stringlist as $key => $value) {
if (array_key_exists($key, $translatedvalues)) {
if ($translatedvalues[$key] !== $value) {
$strings[$value] = $translatedvalues[$key];
}
}
}
return $strings;
}
/**
* Send a cached language pack.
*/
protected function send_cached_pack(): void {
global $CFG;
if (file_exists($this->candidatefile)) {
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
// We do not actually need to verify the etag value because our files
// never change in cache because we increment the rev counter.
$this->send_unmodified_headers(filemtime($this->candidatefile));
}
$this->send_cached($this->candidatefile);
}
}
/**
* Store a langauge cache file containing all of the processed strings.
*
* @param string[] $strings The strings to store
*/
protected function store_lang_file(array $strings): void {
global $CFG;
clearstatcache();
if (!file_exists(dirname($this->candidatefile))) {
@mkdir(dirname($this->candidatefile), $CFG->directorypermissions, true);
}
// Prevent serving of incomplete file from concurrent request,
// the rename() should be more atomic than fwrite().
ignore_user_abort(true);
// First up write out the single file for all those using decent browsers.
$content = json_encode($strings, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES);
$filename = $this->candidatefile;
if ($fp = fopen($filename . '.tmp', 'xb')) {
fwrite($fp, $content);
fclose($fp);
rename($filename . '.tmp', $filename);
@chmod($filename, $CFG->filepermissions);
@unlink($filename . '.tmp'); // Just in case anything fails.
}
ignore_user_abort(false);
if (connection_aborted()) {
die;
}
}
/**
* Check whether the candidate file exists.
*
* @return bool
*/
protected function is_candidate_file_available(): bool {
return file_exists($this->candidatefile);
}
/**
* Get the eTag for the candidate file.
*
* This is a unique hash based on the file arguments.
* It does not need to consider the file content because we use a cache busting URL.
*
* @return string The eTag content
*/
protected function get_etag(): string {
$etag = [
$this->lang,
$this->rev,
];
return sha1(implode('/', $etag));
}
/**
* Send the candidate file, with aggressive cachign headers.
*
* This includdes eTags, a last-modified, and expiry approximately 90 days in the future.
*/
protected function send_cached(): void {
$path = $this->candidatefile;
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Etag: "' . $this->get_etag() . '"');
header('Content-Disposition: inline; filename="lang.php"');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($path)) . ' GMT');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $lifetime) . ' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age=' . $lifetime . ', immutable');
header('Accept-Ranges: none');
header('Content-Type: application/json; charset=utf-8');
if (!min_enable_zlib_compression()) {
header('Content-Length: ' . filesize($path));
}
readfile($path);
die;
}
/**
* Sends the content directly without caching it.
*
* @param string[] $strings
*/
protected function send_uncached(array $strings): void {
header('Content-Disposition: inline; filename="styles_debug.php"');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header('Pragma: ');
header('Accept-Ranges: none');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($strings, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES);
die;
}
/**
* Send file not modified headers.
*
* @param int $lastmodified
*/
protected function send_unmodified_headers($lastmodified): void {
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('HTTP/1.1 304 Not Modified');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $lifetime) . ' GMT');
header('Cache-Control: public, max-age=' . $lifetime);
header('Content-Type: application/json; charset=utf-8');
header('Etag: "' . $this->get_etag() . '"');
if ($lastmodified) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT');
}
die;
}
/**
* Sends a 404 message to indicate that the content was not found.
*
* @param null|string $message An optional informative message to include to help debugging
*/
protected function send_not_found(?string $message = null): void {
header('HTTP/1.0 404 not found');
if ($message) {
die($message);
} else {
die('Language data was not found, sorry.');
}
}
};
$loader = new lang();

View File

@ -24,3 +24,407 @@
$string['pluginname'] = 'TinyMCE editor';
$string['privacy:reason'] = 'The TinyMCE Editor does not store any preferences or user data.';
$string['tiny:hash'] = '#';
$string['tiny:accessibility'] = 'Accessibility';
$string['tiny:action'] = 'Action';
$string['tiny:activity'] = 'Activity';
$string['tiny:address'] = 'Address';
$string['tiny:advanced'] = 'Advanced';
$string['tiny:align'] = 'Align';
$string['tiny:align_center'] = 'Align center';
$string['tiny:align_left'] = 'Align left';
$string['tiny:align_right'] = 'Align right';
$string['tiny:alignment'] = 'Alignment';
$string['tiny:all'] = 'All';
$string['tiny:alternative_description'] = 'Alternative description';
$string['tiny:alternative_source'] = 'Alternative source';
$string['tiny:alternative_source_url'] = 'Alternative source URL';
$string['tiny:anchor'] = 'Anchor';
$string['tiny:anchor...'] = 'Anchor...';
$string['tiny:anchors'] = 'Anchors';
$string['tiny:animals_and_nature'] = 'Animals and Nature';
$string['tiny:arrows'] = 'Arrows';
$string['tiny:b'] = 'B';
$string['tiny:background_color'] = 'Background color';
$string['tiny:black'] = 'Black';
$string['tiny:block'] = 'Block';
$string['tiny:blockquote'] = 'Blockquote';
$string['tiny:blocks'] = 'Blocks';
$string['tiny:blue'] = 'Blue';
$string['tiny:blue_component'] = 'Blue component';
$string['tiny:body'] = 'Body';
$string['tiny:bold'] = 'Bold';
$string['tiny:border'] = 'Border';
$string['tiny:border_color'] = 'Border color';
$string['tiny:border_style'] = 'Border style';
$string['tiny:border_width'] = 'Border width';
$string['tiny:bottom'] = 'Bottom';
$string['tiny:browse_for_an_image'] = 'Browse for an image';
$string['tiny:bullet_list'] = 'Bullet list';
$string['tiny:cancel'] = 'Cancel';
$string['tiny:caption'] = 'Caption';
$string['tiny:cell'] = 'Cell';
$string['tiny:cell_padding'] = 'Cell padding';
$string['tiny:cell_properties'] = 'Cell properties';
$string['tiny:cell_spacing'] = 'Cell spacing';
$string['tiny:cell_styles'] = 'Cell styles';
$string['tiny:cell_type'] = 'Cell type';
$string['tiny:center'] = 'Center';
$string['tiny:characters'] = 'Characters';
$string['tiny:characters_no_spaces'] = 'Characters (no spaces)';
$string['tiny:circle'] = 'Circle';
$string['tiny:class'] = 'Class';
$string['tiny:clear_formatting'] = 'Clear formatting';
$string['tiny:close'] = 'Close';
$string['tiny:code'] = 'Code';
$string['tiny:code_sample...'] = 'Code sample...';
$string['tiny:code_view'] = 'Code view';
$string['tiny:color_picker'] = 'Color Picker';
$string['tiny:color_swatch'] = 'Color swatch';
$string['tiny:cols'] = 'Cols';
$string['tiny:column'] = 'Column';
$string['tiny:column_clipboard_actions'] = 'Column clipboard actions';
$string['tiny:column_group'] = 'Column group';
$string['tiny:column_header'] = 'Column header';
$string['tiny:constrain_proportions'] = 'Constrain proportions';
$string['tiny:copy'] = 'Copy';
$string['tiny:copy_column'] = 'Copy column';
$string['tiny:copy_row'] = 'Copy row';
$string['tiny:could_not_find_the_specified_string.'] = 'Could not find the specified string.';
$string['tiny:could_not_load_emojis'] = 'Could not load emojis';
$string['tiny:count'] = 'Count';
$string['tiny:currency'] = 'Currency';
$string['tiny:current_window'] = 'Current window';
$string['tiny:custom_color'] = 'Custom color';
$string['tiny:custom...'] = 'Custom...';
$string['tiny:cut'] = 'Cut';
$string['tiny:cut_column'] = 'Cut column';
$string['tiny:cut_row'] = 'Cut row';
$string['tiny:dark_blue'] = 'Dark Blue';
$string['tiny:dark_gray'] = 'Dark Gray';
$string['tiny:dark_green'] = 'Dark Green';
$string['tiny:dark_orange'] = 'Dark Orange';
$string['tiny:dark_purple'] = 'Dark Purple';
$string['tiny:dark_red'] = 'Dark Red';
$string['tiny:dark_turquoise'] = 'Dark Turquoise';
$string['tiny:dark_yellow'] = 'Dark Yellow';
$string['tiny:dashed'] = 'Dashed';
$string['tiny:datetime'] = 'Date/time';
$string['tiny:decrease_indent'] = 'Decrease indent';
$string['tiny:default'] = 'Default';
$string['tiny:delete_column'] = 'Delete column';
$string['tiny:delete_row'] = 'Delete row';
$string['tiny:delete_table'] = 'Delete table';
$string['tiny:dimensions'] = 'Dimensions';
$string['tiny:disc'] = 'Disc';
$string['tiny:div'] = 'Div';
$string['tiny:document'] = 'Document';
$string['tiny:dotted'] = 'Dotted';
$string['tiny:double'] = 'Double';
$string['tiny:drop_an_image_here'] = 'Drop an image here';
$string['tiny:dropped_file_type_is_not_supported'] = 'Dropped file type is not supported';
$string['tiny:edit'] = 'Edit';
$string['tiny:embed'] = 'Embed';
$string['tiny:emojis'] = 'Emojis';
$string['tiny:emojis...'] = 'Emojis...';
$string['tiny:error'] = 'Error';
$string['tiny:error_form_submit_field_collision.'] = 'Error: Form submit field collision.';
$string['tiny:error_no_form_element_found.'] = 'Error: No form element found.';
$string['tiny:extended_latin'] = 'Extended Latin';
$string['tiny:failed_to_initialize_plugin_0'] = 'Failed to initialize plugin: {0}';
$string['tiny:failed_to_load_plugin_url_0'] = 'Failed to load plugin url: {0}';
$string['tiny:failed_to_load_plugin_0_from_url_1'] = 'Failed to load plugin: {0} from url {1}';
$string['tiny:failed_to_upload_image_0'] = 'Failed to upload image: {0}';
$string['tiny:file'] = 'File';
$string['tiny:find'] = 'Find';
$string['tiny:find_if_searchreplace_plugin_activated'] = 'Find (if searchreplace plugin activated)';
$string['tiny:find_and_replace'] = 'Find and Replace';
$string['tiny:find_and_replace...'] = 'Find and replace...';
$string['tiny:find_in_selection'] = 'Find in selection';
$string['tiny:find_whole_words_only'] = 'Find whole words only';
$string['tiny:flags'] = 'Flags';
$string['tiny:focus_to_contextual_toolbar'] = 'Focus to contextual toolbar';
$string['tiny:focus_to_element_path'] = 'Focus to element path';
$string['tiny:focus_to_menubar'] = 'Focus to menubar';
$string['tiny:focus_to_toolbar'] = 'Focus to toolbar';
$string['tiny:font'] = 'Font';
$string['tiny:font_sizes'] = 'Font sizes';
$string['tiny:fonts'] = 'Fonts';
$string['tiny:food_and_drink'] = 'Food and Drink';
$string['tiny:footer'] = 'Footer';
$string['tiny:format'] = 'Format';
$string['tiny:formats'] = 'Formats';
$string['tiny:fullscreen'] = 'Fullscreen';
$string['tiny:g'] = 'G';
$string['tiny:general'] = 'General';
$string['tiny:gray'] = 'Gray';
$string['tiny:green'] = 'Green';
$string['tiny:green_component'] = 'Green component';
$string['tiny:groove'] = 'Groove';
$string['tiny:handy_shortcuts'] = 'Handy Shortcuts';
$string['tiny:header'] = 'Header';
$string['tiny:header_cell'] = 'Header cell';
$string['tiny:heading_1'] = 'Heading 1';
$string['tiny:heading_2'] = 'Heading 2';
$string['tiny:heading_3'] = 'Heading 3';
$string['tiny:heading_4'] = 'Heading 4';
$string['tiny:heading_5'] = 'Heading 5';
$string['tiny:heading_6'] = 'Heading 6';
$string['tiny:headings'] = 'Headings';
$string['tiny:height'] = 'Height';
$string['tiny:help'] = 'Help';
$string['tiny:hex_color_code'] = 'Hex color code';
$string['tiny:hidden'] = 'Hidden';
$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:image_is_decorative'] = 'Image is decorative';
$string['tiny:image_list'] = 'Image list';
$string['tiny:image_title'] = 'Image title';
$string['tiny:image...'] = 'Image...';
$string['tiny:imageproxy_http_error_could_not_find_image_proxy'] = 'ImageProxy HTTP error: Could not find Image Proxy';
$string['tiny:imageproxy_http_error_incorrect_image_proxy_url'] = 'ImageProxy HTTP error: Incorrect Image Proxy URL';
$string['tiny:imageproxy_http_error_rejected_request'] = 'ImageProxy HTTP error: Rejected request';
$string['tiny:imageproxy_http_error_unknown_imageproxy_error'] = 'ImageProxy HTTP error: Unknown ImageProxy error';
$string['tiny:increase_indent'] = 'Increase indent';
$string['tiny:inline'] = 'Inline';
$string['tiny:insert'] = 'Insert';
$string['tiny:insert_template'] = 'Insert Template';
$string['tiny:insert_column_after'] = 'Insert column after';
$string['tiny:insert_column_before'] = 'Insert column before';
$string['tiny:insert_datetime'] = 'Insert date/time';
$string['tiny:insert_image'] = 'Insert image';
$string['tiny:insert_link_if_link_plugin_activated'] = 'Insert link (if link plugin activated)';
$string['tiny:insert_row_after'] = 'Insert row after';
$string['tiny:insert_row_before'] = 'Insert row before';
$string['tiny:insert_table'] = 'Insert table';
$string['tiny:insert_template...'] = 'Insert template...';
$string['tiny:insert_video'] = 'Insert video';
$string['tiny:insertedit_code_sample'] = 'Insert/Edit code sample';
$string['tiny:insertedit_image'] = 'Insert/edit image';
$string['tiny:insertedit_link'] = 'Insert/edit link';
$string['tiny:insertedit_media'] = 'Insert/edit media';
$string['tiny:insertedit_video'] = 'Insert/edit video';
$string['tiny:inset'] = 'Inset';
$string['tiny:invalid_hex_color_code_0'] = 'Invalid hex color code: {0}';
$string['tiny:invalid_input'] = 'Invalid input';
$string['tiny:italic'] = 'Italic';
$string['tiny:justify'] = 'Justify';
$string['tiny:keyboard_navigation'] = 'Keyboard Navigation';
$string['tiny:language'] = 'Language';
$string['tiny:learn_more...'] = 'Learn more...';
$string['tiny:left'] = 'Left';
$string['tiny:left_to_right'] = 'Left to right';
$string['tiny:light_blue'] = 'Light Blue';
$string['tiny:light_gray'] = 'Light Gray';
$string['tiny:light_green'] = 'Light Green';
$string['tiny:light_purple'] = 'Light Purple';
$string['tiny:light_red'] = 'Light Red';
$string['tiny:light_yellow'] = 'Light Yellow';
$string['tiny:line_height'] = 'Line height';
$string['tiny:link_list'] = 'Link list';
$string['tiny:link...'] = 'Link...';
$string['tiny:list_properties'] = 'List Properties';
$string['tiny:list_properties...'] = 'List properties...';
$string['tiny:loading_emojis...'] = 'Loading emojis...';
$string['tiny:loading...'] = 'Loading...';
$string['tiny:lower_alpha'] = 'Lower Alpha';
$string['tiny:lower_greek'] = 'Lower Greek';
$string['tiny:lower_roman'] = 'Lower Roman';
$string['tiny:match_case'] = 'Match case';
$string['tiny:mathematical'] = 'Mathematical';
$string['tiny:media_poster_image_url'] = 'Media poster (Image URL)';
$string['tiny:media...'] = 'Media...';
$string['tiny:medium_blue'] = 'Medium Blue';
$string['tiny:medium_gray'] = 'Medium Gray';
$string['tiny:medium_purple'] = 'Medium Purple';
$string['tiny:merge_cells'] = 'Merge cells';
$string['tiny:middle'] = 'Middle';
$string['tiny:midnight_blue'] = 'Midnight Blue';
$string['tiny:more...'] = 'More...';
$string['tiny:name'] = 'Name';
$string['tiny:navy_blue'] = 'Navy Blue';
$string['tiny:new_document'] = 'New document';
$string['tiny:new_window'] = 'New window';
$string['tiny:next'] = 'Next';
$string['tiny:no'] = 'No';
$string['tiny:no_alignment'] = 'No alignment';
$string['tiny:no_color'] = 'No color';
$string['tiny:nonbreaking_space'] = 'Nonbreaking space';
$string['tiny:none'] = 'None';
$string['tiny:numbered_list'] = 'Numbered list';
$string['tiny:or'] = 'OR';
$string['tiny:objects'] = 'Objects';
$string['tiny:ok'] = 'Ok';
$string['tiny:open_help_dialog'] = 'Open help dialog';
$string['tiny:open_link'] = 'Open link';
$string['tiny:open_link_in...'] = 'Open link in...';
$string['tiny:open_popup_menu_for_split_buttons'] = 'Open popup menu for split buttons';
$string['tiny:orange'] = 'Orange';
$string['tiny:outset'] = 'Outset';
$string['tiny:page_break'] = 'Page break';
$string['tiny:paragraph'] = 'Paragraph';
$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_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';
$string['tiny:paste_your_embed_code_below'] = 'Paste your embed code below:';
$string['tiny:people'] = 'People';
$string['tiny:plugins'] = 'Plugins';
$string['tiny:plugins_installed_0'] = 'Plugins installed ({0}):';
$string['tiny:powered_by_0'] = 'Powered by {0}';
$string['tiny:pre'] = 'Pre';
$string['tiny:preferences'] = 'Preferences';
$string['tiny:preformatted'] = 'Preformatted';
$string['tiny:premium_plugins'] = 'Premium plugins:';
$string['tiny:preview'] = 'Preview';
$string['tiny:previous'] = 'Previous';
$string['tiny:print'] = 'Print';
$string['tiny:print...'] = 'Print...';
$string['tiny:purple'] = 'Purple';
$string['tiny:quotations'] = 'Quotations';
$string['tiny:r'] = 'R';
$string['tiny:range_0_to_255'] = 'Range 0 to 255';
$string['tiny:red'] = 'Red';
$string['tiny:red_component'] = 'Red component';
$string['tiny:redo'] = 'Redo';
$string['tiny:remove'] = 'Remove';
$string['tiny:remove_color'] = 'Remove color';
$string['tiny:remove_link'] = 'Remove link';
$string['tiny:replace'] = 'Replace';
$string['tiny:replace_all'] = 'Replace all';
$string['tiny:replace_with'] = 'Replace with';
$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:ridge'] = 'Ridge';
$string['tiny:right'] = 'Right';
$string['tiny:right_to_left'] = 'Right to left';
$string['tiny:row'] = 'Row';
$string['tiny:row_clipboard_actions'] = 'Row clipboard actions';
$string['tiny:row_group'] = 'Row group';
$string['tiny:row_header'] = 'Row header';
$string['tiny:row_properties'] = 'Row properties';
$string['tiny:row_type'] = 'Row type';
$string['tiny:rows'] = 'Rows';
$string['tiny:save'] = 'Save';
$string['tiny:save_if_save_plugin_activated'] = 'Save (if save plugin activated)';
$string['tiny:scope'] = 'Scope';
$string['tiny:search'] = 'Search';
$string['tiny:select_all'] = 'Select all';
$string['tiny:select...'] = 'Select...';
$string['tiny:selection'] = 'Selection';
$string['tiny:shortcut'] = 'Shortcut';
$string['tiny:show_blocks'] = 'Show blocks';
$string['tiny:show_caption'] = 'Show caption';
$string['tiny:show_invisible_characters'] = 'Show invisible characters';
$string['tiny:size'] = 'Size';
$string['tiny:solid'] = 'Solid';
$string['tiny:source'] = 'Source';
$string['tiny:source_code'] = 'Source code';
$string['tiny:special_character'] = 'Special Character';
$string['tiny:special_character...'] = 'Special character...';
$string['tiny:split_cell'] = 'Split cell';
$string['tiny:square'] = 'Square';
$string['tiny:start_list_at_number'] = 'Start list at number';
$string['tiny:strikethrough'] = 'Strikethrough';
$string['tiny:style'] = 'Style';
$string['tiny:subscript'] = 'Subscript';
$string['tiny:superscript'] = 'Superscript';
$string['tiny:switch_to_or_from_fullscreen_mode'] = 'Switch to or from fullscreen mode';
$string['tiny:symbols'] = 'Symbols';
$string['tiny:system_font'] = 'System Font';
$string['tiny:table'] = 'Table';
$string['tiny:table_caption'] = 'Table caption';
$string['tiny:table_properties'] = 'Table properties';
$string['tiny:table_styles'] = 'Table styles';
$string['tiny:template'] = 'Template';
$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:title'] = 'Title';
$string['tiny:to_open_the_popup_press_shiftenter'] = 'To open the popup, press Shift+Enter';
$string['tiny:tools'] = 'Tools';
$string['tiny:top'] = 'Top';
$string['tiny:travel_and_places'] = 'Travel and Places';
$string['tiny:turquoise'] = 'Turquoise';
$string['tiny:underline'] = 'Underline';
$string['tiny:undo'] = 'Undo';
$string['tiny:upload'] = 'Upload';
$string['tiny:uploading_image'] = 'Uploading image';
$string['tiny:upper_alpha'] = 'Upper Alpha';
$string['tiny:upper_roman'] = 'Upper Roman';
$string['tiny:url'] = 'Url';
$string['tiny:user_defined'] = 'User Defined';
$string['tiny:valid'] = 'Valid';
$string['tiny:version'] = 'Version';
$string['tiny:vertical_align'] = 'Vertical align';
$string['tiny:vertical_space'] = 'Vertical space';
$string['tiny:view'] = 'View';
$string['tiny:visual_aids'] = 'Visual aids';
$string['tiny:warn'] = 'Warn';
$string['tiny:white'] = 'White';
$string['tiny:width'] = 'Width';
$string['tiny:word_count'] = 'Word count';
$string['tiny:words'] = 'Words';
$string['tiny:words_0'] = 'Words: {0}';
$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:alignment1'] = 'alignment';
$string['tiny:austral_sign'] = 'austral sign';
$string['tiny:cedi_sign'] = 'cedi sign';
$string['tiny:colon_sign'] = 'colon sign';
$string['tiny:cruzeiro_sign'] = 'cruzeiro sign';
$string['tiny:currency_sign'] = 'currency sign';
$string['tiny:dollar_sign'] = 'dollar sign';
$string['tiny:dong_sign'] = 'dong sign';
$string['tiny:drachma_sign'] = 'drachma sign';
$string['tiny:euro-currency_sign'] = 'euro-currency sign';
$string['tiny:example'] = 'example';
$string['tiny:formatting'] = 'formatting';
$string['tiny:french_franc_sign'] = 'french franc sign';
$string['tiny:german_penny_symbol'] = 'german penny symbol';
$string['tiny:guarani_sign'] = 'guarani sign';
$string['tiny:history'] = 'history';
$string['tiny:hryvnia_sign'] = 'hryvnia sign';
$string['tiny:indentation'] = 'indentation';
$string['tiny:indian_rupee_sign'] = 'indian rupee sign';
$string['tiny:kip_sign'] = 'kip sign';
$string['tiny:lira_sign'] = 'lira sign';
$string['tiny:livre_tournois_sign'] = 'livre tournois sign';
$string['tiny:manat_sign'] = 'manat sign';
$string['tiny:mill_sign'] = 'mill sign';
$string['tiny:naira_sign'] = 'naira sign';
$string['tiny:new_sheqel_sign'] = 'new sheqel sign';
$string['tiny:nordic_mark_sign'] = 'nordic mark sign';
$string['tiny:peseta_sign'] = 'peseta sign';
$string['tiny:peso_sign'] = 'peso sign';
$string['tiny:ruble_sign'] = 'ruble sign';
$string['tiny:rupee_sign'] = 'rupee sign';
$string['tiny:spesmilo_sign'] = 'spesmilo sign';
$string['tiny:styles'] = 'styles';
$string['tiny:tenge_sign'] = 'tenge sign';
$string['tiny:tugrik_sign'] = 'tugrik sign';
$string['tiny:turkish_lira_sign'] = 'turkish lira sign';
$string['tiny:won_sign'] = 'won sign';
$string['tiny:yen_character'] = 'yen character';
$string['tiny:yenyuan_character_variant_one'] = 'yen/yuan character variant one';
$string['tiny:yuan_character'] = 'yuan character';
$string['tiny:yuan_character_in_hong_kong_and_taiwan'] = 'yuan character, in hong kong and taiwan';
$string['tiny:0_characters'] = '{0} characters';
$string['tiny:0_words'] = '{0} words';

View File

@ -40,3 +40,44 @@
```
6. Check the (Release notes)[https://www.tiny.cloud/docs/tinymce/6/release-notes/] for any new plugins, premium plugins, menu items, or buttons and add them to classes/manager.php
## 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.
3. Unzip the translation into a new directory:
```
langdir=`mktemp -d`
cd "${langdir}"
unzip path/to/de.zip
```
4. Run the translation tool:
```
node "${MOODLEDIR}/tools/getOriginals.mjs"
```
This will generate two files
5. Copy the `tinystrings.json` file into the Moodle directory
```
cp tinystrings.json "${MOODLEDIR}/tinystrings.json"
```
6. Copy the content of the `strings.php` file over the existing tiny strings:
```
sed -i "/string\['tiny:/d" "${MOODLEDIR}/lang/en/editor_tiny.php"
cat strings.php >> "${MOODLEDIR}/lang/en/editor_tiny.php"
```
7. Commit changes
---
**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).
---

View File

@ -0,0 +1,406 @@
{
"tiny:hash": "#",
"tiny:accessibility": "Accessibility",
"tiny:action": "Action",
"tiny:activity": "Activity",
"tiny:address": "Address",
"tiny:advanced": "Advanced",
"tiny:align": "Align",
"tiny:align_center": "Align center",
"tiny:align_left": "Align left",
"tiny:align_right": "Align right",
"tiny:alignment": "Alignment",
"tiny:all": "All",
"tiny:alternative_description": "Alternative description",
"tiny:alternative_source": "Alternative source",
"tiny:alternative_source_url": "Alternative source URL",
"tiny:anchor": "Anchor",
"tiny:anchor...": "Anchor...",
"tiny:anchors": "Anchors",
"tiny:animals_and_nature": "Animals and Nature",
"tiny:arrows": "Arrows",
"tiny:b": "B",
"tiny:background_color": "Background color",
"tiny:black": "Black",
"tiny:block": "Block",
"tiny:blockquote": "Blockquote",
"tiny:blocks": "Blocks",
"tiny:blue": "Blue",
"tiny:blue_component": "Blue component",
"tiny:body": "Body",
"tiny:bold": "Bold",
"tiny:border": "Border",
"tiny:border_color": "Border color",
"tiny:border_style": "Border style",
"tiny:border_width": "Border width",
"tiny:bottom": "Bottom",
"tiny:browse_for_an_image": "Browse for an image",
"tiny:bullet_list": "Bullet list",
"tiny:cancel": "Cancel",
"tiny:caption": "Caption",
"tiny:cell": "Cell",
"tiny:cell_padding": "Cell padding",
"tiny:cell_properties": "Cell properties",
"tiny:cell_spacing": "Cell spacing",
"tiny:cell_styles": "Cell styles",
"tiny:cell_type": "Cell type",
"tiny:center": "Center",
"tiny:characters": "Characters",
"tiny:characters_no_spaces": "Characters (no spaces)",
"tiny:circle": "Circle",
"tiny:class": "Class",
"tiny:clear_formatting": "Clear formatting",
"tiny:close": "Close",
"tiny:code": "Code",
"tiny:code_sample...": "Code sample...",
"tiny:code_view": "Code view",
"tiny:color_picker": "Color Picker",
"tiny:color_swatch": "Color swatch",
"tiny:cols": "Cols",
"tiny:column": "Column",
"tiny:column_clipboard_actions": "Column clipboard actions",
"tiny:column_group": "Column group",
"tiny:column_header": "Column header",
"tiny:constrain_proportions": "Constrain proportions",
"tiny:copy": "Copy",
"tiny:copy_column": "Copy column",
"tiny:copy_row": "Copy row",
"tiny:could_not_find_the_specified_string.": "Could not find the specified string.",
"tiny:could_not_load_emojis": "Could not load emojis",
"tiny:count": "Count",
"tiny:currency": "Currency",
"tiny:current_window": "Current window",
"tiny:custom_color": "Custom color",
"tiny:custom...": "Custom...",
"tiny:cut": "Cut",
"tiny:cut_column": "Cut column",
"tiny:cut_row": "Cut row",
"tiny:dark_blue": "Dark Blue",
"tiny:dark_gray": "Dark Gray",
"tiny:dark_green": "Dark Green",
"tiny:dark_orange": "Dark Orange",
"tiny:dark_purple": "Dark Purple",
"tiny:dark_red": "Dark Red",
"tiny:dark_turquoise": "Dark Turquoise",
"tiny:dark_yellow": "Dark Yellow",
"tiny:dashed": "Dashed",
"tiny:datetime": "Date/time",
"tiny:decrease_indent": "Decrease indent",
"tiny:default": "Default",
"tiny:delete_column": "Delete column",
"tiny:delete_row": "Delete row",
"tiny:delete_table": "Delete table",
"tiny:dimensions": "Dimensions",
"tiny:disc": "Disc",
"tiny:div": "Div",
"tiny:document": "Document",
"tiny:dotted": "Dotted",
"tiny:double": "Double",
"tiny:drop_an_image_here": "Drop an image here",
"tiny:dropped_file_type_is_not_supported": "Dropped file type is not supported",
"tiny:edit": "Edit",
"tiny:embed": "Embed",
"tiny:emojis": "Emojis",
"tiny:emojis...": "Emojis...",
"tiny:error": "Error",
"tiny:error_form_submit_field_collision.": "Error: Form submit field collision.",
"tiny:error_no_form_element_found.": "Error: No form element found.",
"tiny:extended_latin": "Extended Latin",
"tiny:failed_to_initialize_plugin_0": "Failed to initialize plugin: {0}",
"tiny:failed_to_load_plugin_url_0": "Failed to load plugin url: {0}",
"tiny:failed_to_load_plugin_0_from_url_1": "Failed to load plugin: {0} from url {1}",
"tiny:failed_to_upload_image_0": "Failed to upload image: {0}",
"tiny:file": "File",
"tiny:find": "Find",
"tiny:find_if_searchreplace_plugin_activated": "Find (if searchreplace plugin activated)",
"tiny:find_and_replace": "Find and Replace",
"tiny:find_and_replace...": "Find and replace...",
"tiny:find_in_selection": "Find in selection",
"tiny:find_whole_words_only": "Find whole words only",
"tiny:flags": "Flags",
"tiny:focus_to_contextual_toolbar": "Focus to contextual toolbar",
"tiny:focus_to_element_path": "Focus to element path",
"tiny:focus_to_menubar": "Focus to menubar",
"tiny:focus_to_toolbar": "Focus to toolbar",
"tiny:font": "Font",
"tiny:font_sizes": "Font sizes",
"tiny:fonts": "Fonts",
"tiny:food_and_drink": "Food and Drink",
"tiny:footer": "Footer",
"tiny:format": "Format",
"tiny:formats": "Formats",
"tiny:fullscreen": "Fullscreen",
"tiny:g": "G",
"tiny:general": "General",
"tiny:gray": "Gray",
"tiny:green": "Green",
"tiny:green_component": "Green component",
"tiny:groove": "Groove",
"tiny:handy_shortcuts": "Handy Shortcuts",
"tiny:header": "Header",
"tiny:header_cell": "Header cell",
"tiny:heading_1": "Heading 1",
"tiny:heading_2": "Heading 2",
"tiny:heading_3": "Heading 3",
"tiny:heading_4": "Heading 4",
"tiny:heading_5": "Heading 5",
"tiny:heading_6": "Heading 6",
"tiny:headings": "Headings",
"tiny:height": "Height",
"tiny:help": "Help",
"tiny:hex_color_code": "Hex color code",
"tiny:hidden": "Hidden",
"tiny:horizontal_align": "Horizontal align",
"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:image_is_decorative": "Image is decorative",
"tiny:image_list": "Image list",
"tiny:image_title": "Image title",
"tiny:image...": "Image...",
"tiny:imageproxy_http_error_could_not_find_image_proxy": "ImageProxy HTTP error: Could not find Image Proxy",
"tiny:imageproxy_http_error_incorrect_image_proxy_url": "ImageProxy HTTP error: Incorrect Image Proxy URL",
"tiny:imageproxy_http_error_rejected_request": "ImageProxy HTTP error: Rejected request",
"tiny:imageproxy_http_error_unknown_imageproxy_error": "ImageProxy HTTP error: Unknown ImageProxy error",
"tiny:increase_indent": "Increase indent",
"tiny:inline": "Inline",
"tiny:insert": "Insert",
"tiny:insert_template": "Insert Template",
"tiny:insert_column_after": "Insert column after",
"tiny:insert_column_before": "Insert column before",
"tiny:insert_datetime": "Insert date/time",
"tiny:insert_image": "Insert image",
"tiny:insert_link_if_link_plugin_activated": "Insert link (if link plugin activated)",
"tiny:insert_row_after": "Insert row after",
"tiny:insert_row_before": "Insert row before",
"tiny:insert_table": "Insert table",
"tiny:insert_template...": "Insert template...",
"tiny:insert_video": "Insert video",
"tiny:insertedit_code_sample": "Insert/Edit code sample",
"tiny:insertedit_image": "Insert/edit image",
"tiny:insertedit_link": "Insert/edit link",
"tiny:insertedit_media": "Insert/edit media",
"tiny:insertedit_video": "Insert/edit video",
"tiny:inset": "Inset",
"tiny:invalid_hex_color_code_0": "Invalid hex color code: {0}",
"tiny:invalid_input": "Invalid input",
"tiny:italic": "Italic",
"tiny:justify": "Justify",
"tiny:keyboard_navigation": "Keyboard Navigation",
"tiny:language": "Language",
"tiny:learn_more...": "Learn more...",
"tiny:left": "Left",
"tiny:left_to_right": "Left to right",
"tiny:light_blue": "Light Blue",
"tiny:light_gray": "Light Gray",
"tiny:light_green": "Light Green",
"tiny:light_purple": "Light Purple",
"tiny:light_red": "Light Red",
"tiny:light_yellow": "Light Yellow",
"tiny:line_height": "Line height",
"tiny:link_list": "Link list",
"tiny:link...": "Link...",
"tiny:list_properties": "List Properties",
"tiny:list_properties...": "List properties...",
"tiny:loading_emojis...": "Loading emojis...",
"tiny:loading...": "Loading...",
"tiny:lower_alpha": "Lower Alpha",
"tiny:lower_greek": "Lower Greek",
"tiny:lower_roman": "Lower Roman",
"tiny:match_case": "Match case",
"tiny:mathematical": "Mathematical",
"tiny:media_poster_image_url": "Media poster (Image URL)",
"tiny:media...": "Media...",
"tiny:medium_blue": "Medium Blue",
"tiny:medium_gray": "Medium Gray",
"tiny:medium_purple": "Medium Purple",
"tiny:merge_cells": "Merge cells",
"tiny:middle": "Middle",
"tiny:midnight_blue": "Midnight Blue",
"tiny:more...": "More...",
"tiny:name": "Name",
"tiny:navy_blue": "Navy Blue",
"tiny:new_document": "New document",
"tiny:new_window": "New window",
"tiny:next": "Next",
"tiny:no": "No",
"tiny:no_alignment": "No alignment",
"tiny:no_color": "No color",
"tiny:nonbreaking_space": "Nonbreaking space",
"tiny:none": "None",
"tiny:numbered_list": "Numbered list",
"tiny:or": "OR",
"tiny:objects": "Objects",
"tiny:ok": "Ok",
"tiny:open_help_dialog": "Open help dialog",
"tiny:open_link": "Open link",
"tiny:open_link_in...": "Open link in...",
"tiny:open_popup_menu_for_split_buttons": "Open popup menu for split buttons",
"tiny:orange": "Orange",
"tiny:outset": "Outset",
"tiny:page_break": "Page break",
"tiny:paragraph": "Paragraph",
"tiny:paste": "Paste",
"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_or_type_a_link": "Paste or type a link",
"tiny:paste_row_after": "Paste row after",
"tiny:paste_row_before": "Paste row before",
"tiny:paste_your_embed_code_below": "Paste your embed code below:",
"tiny:people": "People",
"tiny:plugins": "Plugins",
"tiny:plugins_installed_0": "Plugins installed ({0}):",
"tiny:powered_by_0": "Powered by {0}",
"tiny:pre": "Pre",
"tiny:preferences": "Preferences",
"tiny:preformatted": "Preformatted",
"tiny:premium_plugins": "Premium plugins:",
"tiny:preview": "Preview",
"tiny:previous": "Previous",
"tiny:print": "Print",
"tiny:print...": "Print...",
"tiny:purple": "Purple",
"tiny:quotations": "Quotations",
"tiny:r": "R",
"tiny:range_0_to_255": "Range 0 to 255",
"tiny:red": "Red",
"tiny:red_component": "Red component",
"tiny:redo": "Redo",
"tiny:remove": "Remove",
"tiny:remove_color": "Remove color",
"tiny:remove_link": "Remove link",
"tiny:replace": "Replace",
"tiny:replace_all": "Replace all",
"tiny:replace_with": "Replace with",
"tiny:resize": "Resize",
"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:ridge": "Ridge",
"tiny:right": "Right",
"tiny:right_to_left": "Right to left",
"tiny:row": "Row",
"tiny:row_clipboard_actions": "Row clipboard actions",
"tiny:row_group": "Row group",
"tiny:row_header": "Row header",
"tiny:row_properties": "Row properties",
"tiny:row_type": "Row type",
"tiny:rows": "Rows",
"tiny:save": "Save",
"tiny:save_if_save_plugin_activated": "Save (if save plugin activated)",
"tiny:scope": "Scope",
"tiny:search": "Search",
"tiny:select_all": "Select all",
"tiny:select...": "Select...",
"tiny:selection": "Selection",
"tiny:shortcut": "Shortcut",
"tiny:show_blocks": "Show blocks",
"tiny:show_caption": "Show caption",
"tiny:show_invisible_characters": "Show invisible characters",
"tiny:size": "Size",
"tiny:solid": "Solid",
"tiny:source": "Source",
"tiny:source_code": "Source code",
"tiny:special_character": "Special Character",
"tiny:special_character...": "Special character...",
"tiny:split_cell": "Split cell",
"tiny:square": "Square",
"tiny:start_list_at_number": "Start list at number",
"tiny:strikethrough": "Strikethrough",
"tiny:style": "Style",
"tiny:subscript": "Subscript",
"tiny:superscript": "Superscript",
"tiny:switch_to_or_from_fullscreen_mode": "Switch to or from fullscreen mode",
"tiny:symbols": "Symbols",
"tiny:system_font": "System Font",
"tiny:table": "Table",
"tiny:table_caption": "Table caption",
"tiny:table_properties": "Table properties",
"tiny:table_styles": "Table styles",
"tiny:template": "Template",
"tiny:templates": "Templates",
"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:title": "Title",
"tiny:to_open_the_popup_press_shiftenter": "To open the popup, press Shift+Enter",
"tiny:tools": "Tools",
"tiny:top": "Top",
"tiny:travel_and_places": "Travel and Places",
"tiny:turquoise": "Turquoise",
"tiny:underline": "Underline",
"tiny:undo": "Undo",
"tiny:upload": "Upload",
"tiny:uploading_image": "Uploading image",
"tiny:upper_alpha": "Upper Alpha",
"tiny:upper_roman": "Upper Roman",
"tiny:url": "Url",
"tiny:user_defined": "User Defined",
"tiny:valid": "Valid",
"tiny:version": "Version",
"tiny:vertical_align": "Vertical align",
"tiny:vertical_space": "Vertical space",
"tiny:view": "View",
"tiny:visual_aids": "Visual aids",
"tiny:warn": "Warn",
"tiny:white": "White",
"tiny:width": "Width",
"tiny:word_count": "Word count",
"tiny:words": "Words",
"tiny:words_0": "Words: {0}",
"tiny:yellow": "Yellow",
"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:alignment1": "alignment",
"tiny:austral_sign": "austral sign",
"tiny:cedi_sign": "cedi sign",
"tiny:colon_sign": "colon sign",
"tiny:cruzeiro_sign": "cruzeiro sign",
"tiny:currency_sign": "currency sign",
"tiny:dollar_sign": "dollar sign",
"tiny:dong_sign": "dong sign",
"tiny:drachma_sign": "drachma sign",
"tiny:euro-currency_sign": "euro-currency sign",
"tiny:example": "example",
"tiny:formatting": "formatting",
"tiny:french_franc_sign": "french franc sign",
"tiny:german_penny_symbol": "german penny symbol",
"tiny:guarani_sign": "guarani sign",
"tiny:history": "history",
"tiny:hryvnia_sign": "hryvnia sign",
"tiny:indentation": "indentation",
"tiny:indian_rupee_sign": "indian rupee sign",
"tiny:kip_sign": "kip sign",
"tiny:lira_sign": "lira sign",
"tiny:livre_tournois_sign": "livre tournois sign",
"tiny:manat_sign": "manat sign",
"tiny:mill_sign": "mill sign",
"tiny:naira_sign": "naira sign",
"tiny:new_sheqel_sign": "new sheqel sign",
"tiny:nordic_mark_sign": "nordic mark sign",
"tiny:peseta_sign": "peseta sign",
"tiny:peso_sign": "peso sign",
"tiny:ruble_sign": "ruble sign",
"tiny:rupee_sign": "rupee sign",
"tiny:spesmilo_sign": "spesmilo sign",
"tiny:styles": "styles",
"tiny:tenge_sign": "tenge sign",
"tiny:tugrik_sign": "tugrik sign",
"tiny:turkish_lira_sign": "turkish lira sign",
"tiny:won_sign": "won sign",
"tiny:yen_character": "yen character",
"tiny:yenyuan_character_variant_one": "yen/yuan character variant one",
"tiny:yuan_character": "yuan character",
"tiny:yuan_character_in_hong_kong_and_taiwan": "yuan character, in hong kong and taiwan",
"tiny:0_characters": "{0} characters",
"tiny:0_words": "{0} words"
}

View File

@ -0,0 +1,86 @@
// 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 <http://www.gnu.org/licenses/>.
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');