diff --git a/config/config.json b/config/config.json index 1a9e41f39..4c11f9b4f 100644 --- a/config/config.json +++ b/config/config.json @@ -84,9 +84,9 @@ } }, "font_sizes": [ - 62.5, - 75.89, - 93.75 + 100, + 110, + 120 ], "customurlscheme": "moodlemobile", "siteurl": "", diff --git a/scripts/create_langindex.sh b/scripts/create_langindex.sh new file mode 100755 index 000000000..9a82b64e0 --- /dev/null +++ b/scripts/create_langindex.sh @@ -0,0 +1,327 @@ +#!/bin/bash +source "functions.sh" + +#Saves or updates a key on langindex_old.json +function save_key { + local key=$1 + local found=$2 + + print_ok "$key=$found" + echo "{\"$key\": \"$found\"}" > langindex_old.json + jq -s '.[0] + .[1]' langindex.json langindex_old.json > langindex_new.json + mv langindex_new.json langindex.json +} + +#Removes a key on langindex_old.json +function remove_key { + local key=$1 + + cat langindex.json | jq 'del(."'$key'")' > langindex_new.json + mv langindex_new.json langindex.json + print_ok "Deleted unused key $key" +} + +#Check if and i exists in php file +function exists_in_file { + local file=$1 + local id=$2 + + file=`echo $file | sed s/^mod_workshop_assessment/workshopform/1` + file=`echo $file | sed s/^mod_assign_/assign/1` + file=`echo $file | sed s/^mod_//1` + + completeFile="$LANGPACKSFOLDER/en/$file.php" + if [ -f "$completeFile" ]; then + coincidence=`grep "string\[\'$id\'\]" $completeFile` + if [ ! -z "$coincidence" ]; then + found=$file + return + fi + fi + found=0 +} + +#Checks if a key exists on the original local_moodlemobileapp.php +function exists_in_mobile { + local file='local_moodlemobileapp' + exists_in_file $file $key +} + +function do_match { + match=$1 + filematch="" + + coincidence=`grep "$match" $LANGPACKSFOLDER/en/*.php | wc -l` + if [ $coincidence -eq 1 ]; then + filematch=`grep "$match" $LANGPACKSFOLDER/en/*.php | cut -d'/' -f5 | cut -d'.' -f1` + exists_in_file $filematch $plainid + elif [ $coincidence -gt 0 ] && [ "$#" -gt 1 ]; then + print_message $2 + tput setaf 6 + grep "$match" $LANGPACKSFOLDER/en/*.php + fi +} + +#Find if the id or the value can be found on files to help providing a solution. +function find_matches { + do_match "string\[\'$plainid\'\] = \'$value\'" "Found EXACT match for $key in the following paths" + if [ $coincidence -gt 0 ]; then + case=1 + return + fi + + do_match " = \'$value\'" "Found some string VALUES for $key in the following paths" + if [ $coincidence -gt 0 ]; then + case=2 + return + fi + + do_match "string\[\'$plainid\'\]" "Found some string KEYS for $key in the following paths, value $value" + if [ $coincidence -gt 0 ]; then + case=3 + return + fi + + print_message "No match found for $key add it to local_moodlemobileapp" + save_key $key "local_moodlemobileapp" +} + +function find_single_matches { + do_match "string\[\'$plainid\'\] = \'$value\'" + if [ ! -z $filematch ] && [ $found != 0 ]; then + case=1 + return + fi + + do_match " = \'$value\'" + if [ ! -z $filematch ] && [ $filematch != 'local_moodlemobileapp' ]; then + case=2 + print_message "Found some string VALUES for $key in the following paths $filematch" + tput setaf 6 + grep "$match" $LANGPACKSFOLDER/en/*.php + return + fi + + do_match "string\[\'$plainid\'\]" + if [ ! -z $filematch ] && [ $found != 0 ]; then + case=3 + return + fi +} + + +#Tries to gues the file where the id will be found. +function guess_file { + local key=$1 + local value=$2 + + local type=`echo $key | cut -d'.' -f1` + local component=`echo $key | cut -d'.' -f2` + local plainid=`echo $key | cut -d'.' -f3-` + + if [ -z "$plainid" ]; then + plainid=$component + component='moodle' + fi + + exists_in_file $component $plainid + + if [ $found == 0 ]; then + tempid=`echo $plainid | sed s/^mod_//1` + if [ $component == 'moodle' ] && [ "$tempid" != "$plainid" ]; then + exists_in_file $plainid pluginname + + if [ $found != 0 ]; then + found=$found/pluginname + fi + fi + fi + + # Not found in file, try in local_moodlemobileapp + if [ $found == 0 ]; then + exists_in_mobile + fi + + # Still not found, if only found in one file, use it. + if [ $found == 0 ]; then + find_single_matches + fi + + # Last fallback. + if [ $found == 0 ]; then + exists_in_file 'moodle' $plainid + fi + + if [ $found == 0 ]; then + find_matches + else + save_key $key $found + fi +} + +function current_translation_exists { + local key=$1 + local current=$2 + local file=$3 + + plainid=`echo $key | cut -d'.' -f3-` + + if [ -z "$plainid" ]; then + plainid=`echo $key | cut -d'.' -f2` + fi + + local currentFile=`echo $current | cut -d'/' -f1` + local currentStr=`echo $current | cut -d'/' -f2-` + if [ $currentFile == $current ]; then + currentStr=$plainid + fi + + exists_in_file $currentFile $currentStr + if [ $found == 0 ]; then + # Translation not found. + exec="jq -r .\"$key\" $file" + value=`$exec` + + print_error "Translation of '$currentStr' not found in '$currentFile'" + + guess_file $key "$value" + fi +} + +#Finds if there's a better file where to get the id from. +function find_better_file { + local key=$1 + local value=$2 + local current=$3 + + local type=`echo $key | cut -d'.' -f1` + local component=`echo $key | cut -d'.' -f2` + local plainid=`echo $key | cut -d'.' -f3-` + + if [ -z "$plainid" ]; then + plainid=$component + component='moodle' + fi + + local currentFile=`echo $current | cut -d'/' -f1` + local currentStr=`echo $current | cut -d'/' -f2-` + if [ $currentFile == $current ]; then + currentStr=$plainid + fi + + exists_in_file $component $plainid + if [ $found != 0 ] && [ $currentStr == $plainid ]; then + if [ $found != $currentFile ]; then + print_ok "Key '$key' found in component, no need to replace old '$current'" + fi + + return + fi + + # Still not found, if only found in one file, use it. + if [ $found == 0 ]; then + find_single_matches + fi + + if [ $found != 0 ] && [ $found != $currentFile ] && [ $case -lt 3 ]; then + print_message "Indexed string '$key' found in '$found' better than '$current'" + return + fi + + if [ $currentFile == 'local_moodlemobileapp' ]; then + exists_in_mobile + else + exists_in_file $currentFile $currentStr + fi + + if [ $found == 0 ]; then + print_error "Indexed string '$key' not found on current place '$current'" + if [ $currentFile != 'local_moodlemobileapp' ]; then + print_error "Execute this on AMOS + CPY [$currentStr,$currentFile],[$key,local_moodlemobileapp]" + save_key $key "local_moodlemobileapp" + fi + fi +} + +# Parses the file. +function parse_file { + findbetter=$2 + keys=`jq -r 'keys[]' $1` + for key in $keys; do + # Check if already parsed. + exec="jq -r .\"$key\" langindex.json" + found=`$exec` + + if [ -z "$found" ] || [ "$found" == 'null' ]; then + exec="jq -r .\"$key\" $1" + value=`$exec` + guess_file $key "$value" + else + if [ "$found" == 'donottranslate' ]; then + # Do nothing since is not translatable. + continue + elif [ ! -z "$findbetter" ]; then + exec="jq -r .\"$key\" $1" + value=`$exec` + find_better_file "$key" "$value" "$found" + elif [ "$found" != 'local_moodlemobileapp' ]; then + current_translation_exists "$key" "$found" "$1" + fi + fi + done + + # Do some cleanup + langkeys=`jq -r 'keys[]' langindex.json` + findkeys="${keys[@]}" + for key in $langkeys; do + # Check if already used. + array_contains "$key" "$findkeys" + + if [ -z "$found" ] || [ "$found" == 'null' ]; then + remove_key $key + fi + done +} + +# Checks if an array contains an string. +function array_contains { + local hayjack=$2 + local needle=$1 + found='' + for i in $hayjack; do + if [ "$i" == "$needle" ] ; then + found=$i + return + fi + done +} + +print_title 'Generating language from code...' +gulp lang + +print_title 'Getting languages' +git clone https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER +pushd $LANGPACKSFOLDER +BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_)) +BRANCH=${BRANCHES[${#BRANCHES[@]}-1]} +git checkout $BRANCH +git pull +popd + +print_title 'Processing file' +#Create langindex.json if not exists. +if [ ! -f 'langindex.json' ]; then + echo "{}" > langindex.json +fi + +findbetter=$1 +parse_file '../src/assets/lang/en.json' $findbetter + +echo + +jq -S --indent 2 -s '.[0]' langindex.json > langindex_new.json +mv langindex_new.json langindex.json +rm langindex_old.json + +print_ok 'All done!' \ No newline at end of file diff --git a/scripts/functions.sh b/scripts/functions.sh new file mode 100644 index 000000000..f90399189 --- /dev/null +++ b/scripts/functions.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +LANGPACKSFOLDER='../../moodle-langpacks' + +function check_success_exit { + if [ $? -ne 0 ]; then + print_error "$1" + exit 1 + elif [ "$#" -gt 1 ]; then + print_ok "$2" + fi +} + +function check_success { + if [ $? -ne 0 ]; then + print_error "$1" + elif [ "$#" -gt 1 ]; then + print_ok "$2" + fi +} + +function print_success { + if [ $? -ne 0 ]; then + print_message "$1" + $3=0 + else + print_ok "$2" + fi +} + +function print_error { + tput setaf 1; echo " ERROR: $1"; tput sgr0 +} + +function print_ok { + tput setaf 2; echo " OK: $1"; tput sgr0 + echo +} + +function print_message { + tput setaf 3; echo "-------- $1"; tput sgr0 + echo +} + +function print_title { + stepnumber=$(($stepnumber + 1)) + echo + tput setaf 5; echo "$stepnumber $1"; tput sgr0 + tput setaf 5; echo '=================='; tput sgr0 +} + +function telegram_notify { + if [ ! -z $TELEGRAM_APIKEY ] && [ ! -z $TELEGRAM_CHATID ] ; then + MESSAGE="Travis error: $1%0ABranch: $TRAVIS_BRANCH%0ARepo: $TRAVIS_REPO_SLUG" + URL="https://api.telegram.org/bot$TELEGRAM_APIKEY/sendMessage" + + curl -s -X POST $URL -d chat_id=$TELEGRAM_CHATID -d text="$MESSAGE" + fi +} + +function notify_on_error_exit { + if [ $? -ne 0 ]; then + print_error "$1" + telegram_notify "$1" + exit 1 + fi +} diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php new file mode 100644 index 000000000..668070a60 --- /dev/null +++ b/scripts/lang_functions.php @@ -0,0 +1,474 @@ +. + +/** + * Helper functions converting moodle strings to json. + */ + +function detect_languages($languages, $keys) { + echo "\n\n\n"; + + $all_languages = glob(LANGPACKSFOLDER.'/*' , GLOB_ONLYDIR); + function get_lang_from_dir($dir) { + return str_replace('_', '-', explode('/', $dir)[3]); + } + function get_lang_not_wp($langname) { + return (substr($langname, -3) !== '-wp'); + } + $all_languages = array_map('get_lang_from_dir', $all_languages); + $all_languages = array_filter($all_languages, 'get_lang_not_wp'); + + $detect_lang = array_diff($all_languages, $languages); + $new_langs = array(); + foreach ($detect_lang as $lang) { + reset_translations_strings(); + $new = detect_lang($lang, $keys); + if ($new) { + $new_langs[$lang] = $lang; + } + } + + return $new_langs; +} + +function build_languages($languages, $keys, $added_langs = []) { + // Process the languages. + foreach ($languages as $lang) { + reset_translations_strings(); + $ok = build_lang($lang, $keys); + if ($ok) { + $added_langs[$lang] = $lang; + } + } + + return $added_langs; +} + +function get_langindex_keys() { + $local = 0; + // Process the index file, just once. + $keys = file_get_contents('langindex.json'); + $keys = (array) json_decode($keys); + + foreach ($keys as $key => $value) { + $map = new StdClass(); + if ($value == 'local_moodlemobileapp') { + $map->file = $value; + $map->string = $key; + $local++; + } else { + $exp = explode('/', $value, 2); + $map->file = $exp[0]; + if (count($exp) == 2) { + $map->string = $exp[1]; + } else { + $exp = explode('.', $key, 3); + + if (count($exp) == 3) { + $map->string = $exp[2]; + } else { + $map->string = $exp[1]; + } + } + } + + $keys[$key] = $map; + } + + $total = count($keys); + echo "Total strings to translate $total ($local local)\n"; + + return $keys; +} + +function add_langs_to_config($langs, $config) { + $changed = false; + $config_langs = get_object_vars($config['languages']); + foreach ($langs as $lang) { + if (!isset($config_langs[$lang])) { + $langfoldername = str_replace('-', '_', $lang); + + $string = []; + include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php'); + $config['languages']->$lang = $string['thislanguage']; + $changed = true; + } + } + + if ($changed) { + // Sort languages by key. + $config['languages'] = json_decode( json_encode( $config['languages'] ), true ); + ksort($config['languages']); + $config['languages'] = json_decode( json_encode( $config['languages'] ), false ); + file_put_contents(CONFIG, json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } +} + +function get_langfolder($lang) { + $folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang); + if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) { + return false; + } + + return $folder; +} + +function get_translation_strings($langfoldername, $file, $override_folder = false) { + global $strings; + + if (isset($strings[$file])) { + return $strings[$file]; + } + + $string = import_translation_strings($langfoldername, $file); + $string_override = $override_folder ? import_translation_strings($override_folder, $file) : false; + + if ($string) { + $strings[$file] = $string; + if ($string_override) { + $strings[$file] = array_merge($strings[$file], $string_override); + } + } else if ($string_override) { + $strings[$file] = $string_override; + } else { + $strings[$file] = false; + } + + return $strings[$file]; +} + +function import_translation_strings($langfoldername, $file) { + $path = $langfoldername.'/'.$file.'.php'; + // Apply translations. + if (!file_exists($path)) { + return false; + } + + $string = []; + include($path); + + return $string; +} + +function reset_translations_strings() { + global $strings; + $strings = []; +} + +function build_lang($lang, $keys) { + $langfoldername = get_langfolder($lang); + if (!$langfoldername) { + echo "Cannot translate $lang, folder not found"; + + return false; + } + + if (OVERRIDE_LANG_SUFIX) { + $override_langfolder = get_langfolder($lang.OVERRIDE_LANG_SUFIX); + } else { + $override_langfolder = false; + } + + $total = count($keys); + $local = 0; + + $langparts = explode('-', $lang, 2); + $parentname = $langparts[0] ? $langparts[0] : ""; + $parent = ""; + + echo "Processing $lang"; + // Check parent language exists. + if ($parentname != $lang && get_langfolder($parentname)) { + echo " ($parentname)"; + $parent = $parentname; + } + + $langFile = false; + // Not yet translated. Do not override. + if (file_exists(ASSETSPATH.$lang.'.json')) { + // Load lang files just once. + $langFile = file_get_contents(ASSETSPATH.$lang.'.json'); + $langFile = (array) json_decode($langFile); + } + + $translations = []; + // Add the translation to the array. + foreach ($keys as $key => $value) { + $string = get_translation_strings($langfoldername, $value->file, $override_langfolder); + // Apply translations. + if (!$string) { + if ($value->file == 'donottranslate') { + // Restore it form the json. + if ($langFile && is_array($langFile) && isset($langFile[$key])) { + $translations[$key] = $langFile[$key]; + } else { + // If not present, do not count it in the total. + $total--; + } + + continue; + } + + if (TOTRANSLATE) { + echo "\n\t\tTo translate $value->string on $value->file"; + } + continue; + } + + if (!isset($string[$value->string]) || ($lang == 'en' && $value->file == 'local_moodlemobileapp')) { + // Not yet translated. Do not override. + if ($langFile && is_array($langFile) && isset($langFile[$key])) { + $translations[$key] = $langFile[$key]; + + if ($value->file == 'local_moodlemobileapp') { + $local++; + } + } + if (TOTRANSLATE && !isset($string[$value->string])) { + echo "\n\t\tTo translate $value->string on $value->file"; + } + continue; + } else { + $text = $string[$value->string]; + } + + if ($value->file != 'local_moodlemobileapp') { + $text = str_replace('$a->', '$a.', $text); + $text = str_replace('{$a', '{{$a', $text); + $text = str_replace('}', '}}', $text); + // Prevent double. + $text = str_replace(array('{{{', '}}}'), array('{{', '}}'), $text); + } else { + // @TODO: Remove that line when core.cannotconnect and core.login.invalidmoodleversion are completelly changed to use $a + if (($key == 'core.cannotconnect' || $key == 'core.login.invalidmoodleversion') && strpos($text, '2.4') != false) { + $text = str_replace('2.4', '{{$a}}', $text); + } + $local++; + } + + $translations[$key] = html_entity_decode($text); + } + + if (!empty($parent)) { + $translations['core.parentlanguage'] = $parent; + } else if (isset($translations['core.parentlanguage'])) { + unset($translations['core.parentlanguage']); + } + + // Sort and save. + ksort($translations); + file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT))); + + $success = count($translations); + $percentage = floor($success/$total * 100); + $bar = progressbar($percentage); + if (strlen($lang) <= 2 && !$parent) { + echo "\t"; + } + echo "\t\t$success of $total -> $percentage% $bar ($local local)\n"; + + if ($lang == 'en') { + generate_local_moodlemobileapp($keys, $translations); + override_component_lang_files($keys, $translations); + } + + return true; +} + +function progressbar($percentage) { + $done = floor($percentage/10); + return "\t".str_repeat('=', $done) . str_repeat('-', 10-$done); +} + +function detect_lang($lang, $keys) { + $langfoldername = get_langfolder($lang); + if (!$langfoldername) { + echo "Cannot translate $lang, folder not found"; + + return false; + } + + $total = count ($keys); + $success = 0; + $local = 0; + + $string = get_translation_strings($langfoldername, 'langconfig'); + $parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : ""; + if (!isset($string['thislanguage'])) { + echo "Cannot translate $lang, translated name not found"; + return false; + } + + $title = $lang; + if ($parent != "" && $parent != $lang) { + $title .= " ($parent)"; + } + $langname = $string['thislanguage']; + $title .= " ".$langname." -D"; + + // Add the translation to the array. + foreach ($keys as $key => $value) { + $string = get_translation_strings($langfoldername, $value->file); + // Apply translations. + if (!$string) { + // Do not count non translatable in the totals. + if ($value->file == 'donottranslate') { + $total--; + } + continue; + } + + if (!isset($string[$value->string])) { + continue; + } else { + $text = $string[$value->string]; + } + + if ($value->file == 'local_moodlemobileapp') { + $local++; + } + + $success++; + } + + $percentage = floor($success/$total * 100); + $bar = progressbar($percentage); + + echo "Checking ".$title.str_repeat("\t", 7 - floor(mb_strlen($title, 'UTF-8')/8)); + echo "\t$success of $total -> $percentage% $bar ($local local)"; + if (($percentage > 75 && $local > 50) || ($percentage > 50 && $local > 75)) { + echo " \t DETECTED\n"; + return true; + } + echo "\n"; + + return false; +} + +function save_key($key, $value, $path) { + $filePath = $path . '/en.json'; + + $file = file_get_contents($filePath); + $file = (array) json_decode($file); + $value = html_entity_decode($value); + if (!isset($file[$key]) || $file[$key] != $value) { + $file[$key] = $value; + ksort($file); + file_put_contents($filePath, str_replace('\/', '/', json_encode($file, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT))); + } +} + +function override_component_lang_files($keys, $translations) { + echo "Override component lang files.\n"; + foreach ($translations as $key => $value) { + $path = '../src/'; + $exp = explode('.', $key, 3); + + $type = $exp[0]; + if (count($exp) == 3) { + $component = $exp[1]; + $plainid = $exp[2]; + } else { + $component = 'moodle'; + $plainid = $exp[1]; + } + switch($type) { + case 'core': + case 'addon': + switch($component) { + case 'moodle': + $path .= 'lang'; + break; + default: + $path .= $type.'/'.str_replace('_', '/', $component).'/lang'; + break; + } + break; + case 'assets': + $path .= $type.'/'.$component; + break; + + } + + if (is_file($path.'/en.json')) { + save_key($plainid, $value, $path); + } + } +} + +/** + * Generates local moodle mobile app file to update languages in AMOS. + * + * @param [array] $keys Translation keys. + * @param [array] $translations English translations. + */ +function generate_local_moodlemobileapp($keys, $translations) { + echo "Generate local_moodlemobileapp.\n"; + $string = '. + +/** + * Version details. + * + * @package local + * @subpackage moodlemobileapp + * @copyright 2014 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string[\'appstoredescription\'] = \'NOTE: This official Moodle Mobile app will ONLY work with Moodle sites that have been set up to allow it. Please talk to your Moodle administrator if you have any problems connecting. + +If your Moodle site has been configured correctly, you can use this app to: + +- browse the content of your courses, even when offline +- receive instant notifications of messages and other events +- quickly find and contact other people in your courses +- upload images, audio, videos and other files from your mobile device +- view your course grades +- and more! + +Please see http://docs.moodle.org/en/Mobile_app for all the latest information. + +We’d really appreciate any good reviews about the functionality so far, and your suggestions on what else you want this app to do! + +The app requires the following permissions: +Record audio - For recording audio to upload to Moodle +Read and modify the contents of your SD card - Contents are downloaded to the SD Card so you can see them offline +Network access - To be able to connect with your Moodle site and check if you are connected or not to switch to offline mode +Run at startup - So you receive local notifications even when the app is running in the background +Prevent phone from sleeping - So you can receive push notifications anytime\';'."\n"; + foreach ($keys as $key => $value) { + if (isset($translations[$key]) && $value->file == 'local_moodlemobileapp') { + $string .= '$string[\''.$key.'\'] = \''.str_replace("'", "\'", $translations[$key]).'\';'."\n"; + } + } + $string .= '$string[\'pluginname\'] = \'Moodle Mobile language strings\';'."\n"; + + file_put_contents('../../moodle-local_moodlemobileapp/lang/en/local_moodlemobileapp.php', $string."\n"); +} diff --git a/scripts/langindex.json b/scripts/langindex.json new file mode 100644 index 000000000..1e1606b32 --- /dev/null +++ b/scripts/langindex.json @@ -0,0 +1,2193 @@ +{ + "addon.badges.alignment": "badges", + "addon.badges.badgedetails": "badges", + "addon.badges.badges": "badges", + "addon.badges.bendorsement": "badges", + "addon.badges.claimcomment": "badges", + "addon.badges.claimid": "badges", + "addon.badges.contact": "badges", + "addon.badges.dateawarded": "badges", + "addon.badges.expired": "badges", + "addon.badges.expirydate": "badges", + "addon.badges.imageauthoremail": "badges", + "addon.badges.imageauthorname": "badges", + "addon.badges.imageauthorurl": "badges", + "addon.badges.imagecaption": "badges", + "addon.badges.issuancedetails": "badges", + "addon.badges.issuerdetails": "badges", + "addon.badges.issueremail": "badges", + "addon.badges.issuername": "badges", + "addon.badges.issuerurl": "badges", + "addon.badges.language": "badges", + "addon.badges.noalignment": "badges", + "addon.badges.nobadges": "badges", + "addon.badges.norelated": "badges", + "addon.badges.recipientdetails": "badges", + "addon.badges.relatedbages": "badges", + "addon.badges.version": "badges", + "addon.badges.warnexpired": "badges", + "addon.block_activitymodules.pluginname": "block_activity_modules", + "addon.block_activityresults.pluginname": "block_activity_results", + "addon.block_badges.pluginname": "block_badges", + "addon.block_blogmenu.pluginname": "block_blog_menu", + "addon.block_blogrecent.pluginname": "block_blog_recent", + "addon.block_blogtags.pluginname": "block_blog_tags", + "addon.block_calendarmonth.pluginname": "block_calendar_month", + "addon.block_calendarupcoming.pluginname": "block_calendar_upcoming", + "addon.block_comments.pluginname": "block_comments", + "addon.block_completionstatus.pluginname": "block_completionstatus", + "addon.block_glossaryrandom.pluginname": "block_glossary_random", + "addon.block_learningplans.pluginname": "block_lp", + "addon.block_myoverview.all": "block_myoverview", + "addon.block_myoverview.allincludinghidden": "block_myoverview", + "addon.block_myoverview.favourites": "block_myoverview", + "addon.block_myoverview.future": "block_myoverview", + "addon.block_myoverview.hiddencourses": "block_myoverview", + "addon.block_myoverview.inprogress": "block_myoverview", + "addon.block_myoverview.lastaccessed": "block_myoverview", + "addon.block_myoverview.nocourses": "block_myoverview", + "addon.block_myoverview.past": "block_myoverview", + "addon.block_myoverview.pluginname": "block_myoverview", + "addon.block_myoverview.shortname": "block_myoverview", + "addon.block_myoverview.title": "block_myoverview", + "addon.block_newsitems.pluginname": "block_news_items", + "addon.block_onlineusers.pluginname": "block_online_users", + "addon.block_privatefiles.pluginname": "block_private_files", + "addon.block_recentactivity.pluginname": "block_recent_activity", + "addon.block_recentlyaccessedcourses.nocourses": "block_recentlyaccessedcourses", + "addon.block_recentlyaccessedcourses.pluginname": "block_recentlyaccessedcourses", + "addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems", + "addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems", + "addon.block_rssclient.pluginname": "block_rss_client", + "addon.block_selfcompletion.pluginname": "block_selfcompletion", + "addon.block_sitemainmenu.pluginname": "block_site_main_menu", + "addon.block_starredcourses.nocourses": "block_starredcourses", + "addon.block_starredcourses.pluginname": "block_starredcourses", + "addon.block_tags.pluginname": "block_tags", + "addon.block_timeline.duedate": "block_timeline", + "addon.block_timeline.next30days": "block_timeline", + "addon.block_timeline.next3months": "block_timeline", + "addon.block_timeline.next6months": "block_timeline", + "addon.block_timeline.next7days": "block_timeline", + "addon.block_timeline.nocoursesinprogress": "block_timeline", + "addon.block_timeline.noevents": "block_timeline", + "addon.block_timeline.overdue": "block_timeline", + "addon.block_timeline.pluginname": "block_timeline", + "addon.block_timeline.sortbycourses": "block_timeline", + "addon.block_timeline.sortbydates": "block_timeline", + "addon.blog.blog": "blog", + "addon.blog.blogentries": "blog", + "addon.blog.errorloadentries": "local_moodlemobileapp", + "addon.blog.linktooriginalentry": "blog", + "addon.blog.noentriesyet": "blog", + "addon.blog.publishtonoone": "blog", + "addon.blog.publishtosite": "blog", + "addon.blog.publishtoworld": "blog", + "addon.blog.showonlyyourentries": "local_moodlemobileapp", + "addon.blog.siteblogheading": "blog", + "addon.calendar.allday": "calendar", + "addon.calendar.calendar": "calendar", + "addon.calendar.calendarevent": "local_moodlemobileapp", + "addon.calendar.calendarevents": "local_moodlemobileapp", + "addon.calendar.calendarreminders": "local_moodlemobileapp", + "addon.calendar.categoryevents": "calendar", + "addon.calendar.confirmeventdelete": "calendar", + "addon.calendar.confirmeventseriesdelete": "calendar", + "addon.calendar.courseevents": "calendar", + "addon.calendar.currentmonth": "local_moodlemobileapp", + "addon.calendar.daynext": "calendar", + "addon.calendar.dayprev": "calendar", + "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", + "addon.calendar.deleteallevents": "calendar", + "addon.calendar.deleteevent": "calendar", + "addon.calendar.deleteoneevent": "calendar", + "addon.calendar.durationminutes": "calendar", + "addon.calendar.durationnone": "calendar", + "addon.calendar.durationuntil": "calendar", + "addon.calendar.editevent": "calendar", + "addon.calendar.errorloadevent": "local_moodlemobileapp", + "addon.calendar.errorloadevents": "local_moodlemobileapp", + "addon.calendar.eventcalendareventdeleted": "calendar", + "addon.calendar.eventduration": "calendar", + "addon.calendar.eventendtime": "calendar", + "addon.calendar.eventkind": "calendar", + "addon.calendar.eventname": "calendar", + "addon.calendar.eventstarttime": "calendar", + "addon.calendar.eventtype": "calendar", + "addon.calendar.fri": "calendar", + "addon.calendar.friday": "calendar", + "addon.calendar.gotoactivity": "calendar", + "addon.calendar.groupevents": "calendar", + "addon.calendar.invalidtimedurationminutes": "calendar", + "addon.calendar.invalidtimedurationuntil": "calendar", + "addon.calendar.mon": "calendar", + "addon.calendar.monday": "calendar", + "addon.calendar.monthlyview": "calendar", + "addon.calendar.newevent": "calendar", + "addon.calendar.noevents": "local_moodlemobileapp", + "addon.calendar.nopermissiontoupdatecalendar": "error", + "addon.calendar.reminders": "local_moodlemobileapp", + "addon.calendar.repeatedevents": "calendar", + "addon.calendar.repeateditall": "calendar", + "addon.calendar.repeateditthis": "calendar", + "addon.calendar.repeatevent": "calendar", + "addon.calendar.repeatweeksl": "calendar", + "addon.calendar.sat": "calendar", + "addon.calendar.saturday": "calendar", + "addon.calendar.setnewreminder": "local_moodlemobileapp", + "addon.calendar.siteevents": "calendar", + "addon.calendar.sun": "calendar", + "addon.calendar.sunday": "calendar", + "addon.calendar.thu": "calendar", + "addon.calendar.thursday": "calendar", + "addon.calendar.today": "calendar", + "addon.calendar.tomorrow": "calendar", + "addon.calendar.tue": "calendar", + "addon.calendar.tuesday": "calendar", + "addon.calendar.typecategory": "calendar", + "addon.calendar.typeclose": "calendar", + "addon.calendar.typecourse": "calendar", + "addon.calendar.typedue": "calendar", + "addon.calendar.typegradingdue": "calendar", + "addon.calendar.typegroup": "calendar", + "addon.calendar.typeopen": "calendar", + "addon.calendar.typesite": "calendar", + "addon.calendar.typeuser": "calendar", + "addon.calendar.upcomingevents": "calendar", + "addon.calendar.userevents": "calendar", + "addon.calendar.wed": "calendar", + "addon.calendar.wednesday": "calendar", + "addon.calendar.when": "calendar", + "addon.calendar.yesterday": "calendar", + "addon.competency.activities": "tool_lp", + "addon.competency.competencies": "competency", + "addon.competency.competenciesmostoftennotproficientincourse": "tool_lp", + "addon.competency.coursecompetencies": "tool_lp", + "addon.competency.coursecompetencyratingsarenotpushedtouserplans": "tool_lp", + "addon.competency.coursecompetencyratingsarepushedtouserplans": "tool_lp", + "addon.competency.crossreferencedcompetencies": "tool_lp", + "addon.competency.duedate": "tool_lp", + "addon.competency.errornocompetenciesfound": "local_moodlemobileapp", + "addon.competency.evidence": "tool_lp", + "addon.competency.evidence_competencyrule": "competency", + "addon.competency.evidence_coursecompleted": "competency", + "addon.competency.evidence_coursemodulecompleted": "competency", + "addon.competency.evidence_courserestored": "competency", + "addon.competency.evidence_evidenceofpriorlearninglinked": "competency", + "addon.competency.evidence_evidenceofpriorlearningunlinked": "competency", + "addon.competency.evidence_manualoverride": "competency", + "addon.competency.evidence_manualoverrideincourse": "competency", + "addon.competency.evidence_manualoverrideinplan": "competency", + "addon.competency.learningplancompetencies": "tool_lp", + "addon.competency.learningplans": "tool_lp", + "addon.competency.myplans": "tool_lp", + "addon.competency.noactivities": "tool_lp", + "addon.competency.nocompetencies": "local_moodlemobileapp", + "addon.competency.nocompetenciesincourse": "tool_lp", + "addon.competency.nocrossreferencedcompetencies": "tool_lp", + "addon.competency.noevidence": "tool_lp", + "addon.competency.noplanswerecreated": "tool_lp", + "addon.competency.nouserplanswithcompetency": "competency", + "addon.competency.path": "tool_lp", + "addon.competency.planstatusactive": "competency", + "addon.competency.planstatuscomplete": "competency", + "addon.competency.planstatusdraft": "competency", + "addon.competency.planstatusinreview": "competency", + "addon.competency.planstatuswaitingforreview": "competency", + "addon.competency.proficient": "tool_lp", + "addon.competency.progress": "tool_lp", + "addon.competency.rating": "tool_lp", + "addon.competency.reviewstatus": "tool_lp", + "addon.competency.status": "tool_lp", + "addon.competency.template": "tool_lp", + "addon.competency.uponcoursecompletion": "tool_lp", + "addon.competency.usercompetencystatus_idle": "competency", + "addon.competency.usercompetencystatus_inreview": "competency", + "addon.competency.usercompetencystatus_waitingforreview": "competency", + "addon.competency.userplans": "competency", + "addon.competency.xcompetenciesproficientoutofy": "tool_lp", + "addon.competency.xcompetenciesproficientoutofyincourse": "tool_lp", + "addon.coursecompletion.complete": "local_moodlemobileapp", + "addon.coursecompletion.completecourse": "block_selfcompletion", + "addon.coursecompletion.completed": "completion", + "addon.coursecompletion.completiondate": "report_completion", + "addon.coursecompletion.completionmenuitem": "completion", + "addon.coursecompletion.couldnotloadreport": "local_moodlemobileapp", + "addon.coursecompletion.coursecompletion": "completion", + "addon.coursecompletion.criteria": "completion", + "addon.coursecompletion.criteriagroup": "completion", + "addon.coursecompletion.criteriarequiredall": "completion", + "addon.coursecompletion.criteriarequiredany": "completion", + "addon.coursecompletion.inprogress": "completion", + "addon.coursecompletion.manualselfcompletion": "completion", + "addon.coursecompletion.nottracked": "completion", + "addon.coursecompletion.notyetstarted": "completion", + "addon.coursecompletion.pending": "completion", + "addon.coursecompletion.required": "moodle", + "addon.coursecompletion.requiredcriteria": "completion", + "addon.coursecompletion.requirement": "block_completionstatus", + "addon.coursecompletion.status": "moodle", + "addon.coursecompletion.viewcoursereport": "completion", + "addon.files.couldnotloadfiles": "local_moodlemobileapp", + "addon.files.emptyfilelist": "local_moodlemobileapp", + "addon.files.erroruploadnotworking": "local_moodlemobileapp", + "addon.files.files": "moodle", + "addon.files.privatefiles": "moodle", + "addon.files.sitefiles": "moodle", + "addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp", + "addon.messages.acceptandaddcontact": "message", + "addon.messages.addcontact": "message", + "addon.messages.addcontactconfirm": "message", + "addon.messages.addtofavourites": "message", + "addon.messages.addtoyourcontacts": "message", + "addon.messages.blocknoncontacts": "message", + "addon.messages.blockuser": "message", + "addon.messages.blockuserconfirm": "message", + "addon.messages.contactableprivacy": "message", + "addon.messages.contactableprivacy_coursemember": "message", + "addon.messages.contactableprivacy_onlycontacts": "message", + "addon.messages.contactableprivacy_site": "message", + "addon.messages.contactblocked": "message", + "addon.messages.contactlistempty": "local_moodlemobileapp", + "addon.messages.contactname": "local_moodlemobileapp", + "addon.messages.contactrequestsent": "message", + "addon.messages.contacts": "message", + "addon.messages.conversationactions": "message", + "addon.messages.decline": "message", + "addon.messages.deleteallconfirm": "message", + "addon.messages.deleteallselfconfirm": "message", + "addon.messages.deleteconversation": "message", + "addon.messages.deleteforeveryone": "message", + "addon.messages.deletemessage": "local_moodlemobileapp", + "addon.messages.deletemessageconfirmation": "local_moodlemobileapp", + "addon.messages.errordeletemessage": "local_moodlemobileapp", + "addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp", + "addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp", + "addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp", + "addon.messages.errorwhileretrievingusers": "local_moodlemobileapp", + "addon.messages.groupconversations": "message", + "addon.messages.groupinfo": "message", + "addon.messages.individualconversations": "message", + "addon.messages.info": "message", + "addon.messages.isnotinyourcontacts": "message", + "addon.messages.message": "message", + "addon.messages.messagenotsent": "local_moodlemobileapp", + "addon.messages.messagepreferences": "message", + "addon.messages.messages": "message", + "addon.messages.muteconversation": "message", + "addon.messages.mutedconversation": "message", + "addon.messages.newmessage": "message", + "addon.messages.newmessages": "local_moodlemobileapp", + "addon.messages.nocontactrequests": "message", + "addon.messages.nocontactsgetstarted": "message", + "addon.messages.nofavourites": "message", + "addon.messages.nogroupconversations": "message", + "addon.messages.noindividualconversations": "message", + "addon.messages.nomessagesfound": "message", + "addon.messages.noncontacts": "message", + "addon.messages.nousersfound": "local_moodlemobileapp", + "addon.messages.numparticipants": "message", + "addon.messages.removecontact": "message", + "addon.messages.removecontactconfirm": "message", + "addon.messages.removefromfavourites": "message", + "addon.messages.removefromyourcontacts": "message", + "addon.messages.requests": "moodle", + "addon.messages.requirecontacttomessage": "message", + "addon.messages.searchcombined": "message", + "addon.messages.selfconversation": "message", + "addon.messages.selfconversationdefaultmessage": "message", + "addon.messages.sendcontactrequest": "message", + "addon.messages.showdeletemessages": "local_moodlemobileapp", + "addon.messages.type_blocked": "local_moodlemobileapp", + "addon.messages.type_offline": "local_moodlemobileapp", + "addon.messages.type_online": "local_moodlemobileapp", + "addon.messages.type_search": "local_moodlemobileapp", + "addon.messages.type_strangers": "local_moodlemobileapp", + "addon.messages.unabletomessage": "message", + "addon.messages.unblockuser": "message", + "addon.messages.unblockuserconfirm": "message", + "addon.messages.unmuteconversation": "message", + "addon.messages.useentertosend": "message", + "addon.messages.useentertosenddescdesktop": "local_moodlemobileapp", + "addon.messages.useentertosenddescmac": "local_moodlemobileapp", + "addon.messages.userwouldliketocontactyou": "message", + "addon.messages.warningconversationmessagenotsent": "local_moodlemobileapp", + "addon.messages.warningmessagenotsent": "local_moodlemobileapp", + "addon.messages.wouldliketocontactyou": "message", + "addon.messages.you": "message", + "addon.messages.youhaveblockeduser": "message", + "addon.messages.yourcontactrequestpending": "message", + "addon.mod_assign.acceptsubmissionstatement": "local_moodlemobileapp", + "addon.mod_assign.addattempt": "assign", + "addon.mod_assign.addnewattempt": "assign", + "addon.mod_assign.addnewattemptfromprevious": "assign", + "addon.mod_assign.addsubmission": "assign", + "addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary": "assign", + "addon.mod_assign.allowsubmissionsfromdate": "assign", + "addon.mod_assign.allowsubmissionsfromdatesummary": "assign", + "addon.mod_assign.applytoteam": "assign", + "addon.mod_assign.assignmentisdue": "assign", + "addon.mod_assign.attemptnumber": "assign", + "addon.mod_assign.attemptreopenmethod": "assign", + "addon.mod_assign.attemptreopenmethod_manual": "assign", + "addon.mod_assign.attemptreopenmethod_untilpass": "assign", + "addon.mod_assign.attemptsettings": "assign", + "addon.mod_assign.cannoteditduetostatementsubmission": "local_moodlemobileapp", + "addon.mod_assign.cannotgradefromapp": "local_moodlemobileapp", + "addon.mod_assign.cannotsubmitduetostatementsubmission": "local_moodlemobileapp", + "addon.mod_assign.confirmsubmission": "assign", + "addon.mod_assign.currentattempt": "assign", + "addon.mod_assign.currentattemptof": "assign", + "addon.mod_assign.currentgrade": "assign", + "addon.mod_assign.cutoffdate": "assign", + "addon.mod_assign.defaultteam": "assign", + "addon.mod_assign.duedate": "assign", + "addon.mod_assign.duedateno": "assign", + "addon.mod_assign.duedatereached": "assign", + "addon.mod_assign.editingstatus": "assign", + "addon.mod_assign.editsubmission": "assign", + "addon.mod_assign.erroreditpluginsnotsupported": "local_moodlemobileapp", + "addon.mod_assign.errorshowinginformation": "local_moodlemobileapp", + "addon.mod_assign.extensionduedate": "assign", + "addon.mod_assign.feedbacknotsupported": "local_moodlemobileapp", + "addon.mod_assign.grade": "grades", + "addon.mod_assign.graded": "assign", + "addon.mod_assign.gradedby": "assign", + "addon.mod_assign.gradedfollowupsubmit": "assign", + "addon.mod_assign.gradedon": "assign", + "addon.mod_assign.gradelocked": "assign", + "addon.mod_assign.gradenotsynced": "local_moodlemobileapp", + "addon.mod_assign.gradeoutof": "assign", + "addon.mod_assign.gradingstatus": "assign", + "addon.mod_assign.groupsubmissionsettings": "assign", + "addon.mod_assign.hiddenuser": "assign", + "addon.mod_assign.latesubmissions": "assign", + "addon.mod_assign.latesubmissionsaccepted": "assign", + "addon.mod_assign.markingworkflowstate": "assign", + "addon.mod_assign.markingworkflowstateinmarking": "assign", + "addon.mod_assign.markingworkflowstateinreview": "assign", + "addon.mod_assign.markingworkflowstatenotmarked": "assign", + "addon.mod_assign.markingworkflowstatereadyforrelease": "assign", + "addon.mod_assign.markingworkflowstatereadyforreview": "assign", + "addon.mod_assign.markingworkflowstatereleased": "assign", + "addon.mod_assign.modulenameplural": "assign", + "addon.mod_assign.multipleteams": "assign", + "addon.mod_assign.multipleteams_desc": "assign", + "addon.mod_assign.noattempt": "assign", + "addon.mod_assign.nomoresubmissionsaccepted": "assign", + "addon.mod_assign.noonlinesubmissions": "assign", + "addon.mod_assign.nosubmission": "assign", + "addon.mod_assign.notallparticipantsareshown": "local_moodlemobileapp", + "addon.mod_assign.noteam": "assign", + "addon.mod_assign.noteam_desc": "assign", + "addon.mod_assign.notgraded": "assign", + "addon.mod_assign.numberofdraftsubmissions": "assign", + "addon.mod_assign.numberofparticipants": "assign", + "addon.mod_assign.numberofsubmissionsneedgrading": "assign", + "addon.mod_assign.numberofsubmittedassignments": "assign", + "addon.mod_assign.numberofteams": "assign", + "addon.mod_assign.numwords": "moodle", + "addon.mod_assign.outof": "assign", + "addon.mod_assign.overdue": "assign", + "addon.mod_assign.submission": "assign", + "addon.mod_assign.submissioneditable": "assign", + "addon.mod_assign.submissionnoteditable": "assign", + "addon.mod_assign.submissionnotsupported": "local_moodlemobileapp", + "addon.mod_assign.submissionslocked": "assign", + "addon.mod_assign.submissionstatus": "assign", + "addon.mod_assign.submissionstatus_": "assign", + "addon.mod_assign.submissionstatus_draft": "assign", + "addon.mod_assign.submissionstatus_marked": "assign", + "addon.mod_assign.submissionstatus_new": "assign", + "addon.mod_assign.submissionstatus_reopened": "assign", + "addon.mod_assign.submissionstatus_submitted": "assign", + "addon.mod_assign.submissionstatusheading": "assign", + "addon.mod_assign.submissionteam": "assign", + "addon.mod_assign.submitassignment": "assign", + "addon.mod_assign.submitassignment_help": "assign", + "addon.mod_assign.submittedearly": "assign", + "addon.mod_assign.submittedlate": "assign", + "addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp", + "addon.mod_assign.timemodified": "assign", + "addon.mod_assign.timeremaining": "assign", + "addon.mod_assign.ungroupedusers": "assign", + "addon.mod_assign.ungroupedusersoptional": "assign", + "addon.mod_assign.unlimitedattempts": "assign", + "addon.mod_assign.userswhoneedtosubmit": "assign", + "addon.mod_assign.userwithid": "local_moodlemobileapp", + "addon.mod_assign.viewsubmission": "assign", + "addon.mod_assign.warningsubmissiongrademodified": "local_moodlemobileapp", + "addon.mod_assign.warningsubmissionmodified": "local_moodlemobileapp", + "addon.mod_assign.wordlimit": "assignsubmission_onlinetext", + "addon.mod_assign_feedback_comments.pluginname": "assignfeedback_comments", + "addon.mod_assign_feedback_editpdf.pluginname": "assignfeedback_editpdf", + "addon.mod_assign_feedback_file.pluginname": "assignfeedback_file", + "addon.mod_assign_submission_comments.pluginname": "assignsubmission_comments", + "addon.mod_assign_submission_file.pluginname": "assignsubmission_file", + "addon.mod_assign_submission_onlinetext.pluginname": "assignsubmission_onlinetext", + "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "assignsubmission_onlinetext", + "addon.mod_book.errorchapter": "book", + "addon.mod_book.modulenameplural": "book", + "addon.mod_book.navnexttitle": "book", + "addon.mod_book.navprevtitle": "book", + "addon.mod_book.tagarea_book_chapters": "book", + "addon.mod_book.toc": "book", + "addon.mod_chat.beep": "chat", + "addon.mod_chat.chatreport": "chat", + "addon.mod_chat.currentusers": "chat", + "addon.mod_chat.enterchat": "chat", + "addon.mod_chat.entermessage": "chat", + "addon.mod_chat.errorwhileconnecting": "local_moodlemobileapp", + "addon.mod_chat.errorwhilegettingchatdata": "local_moodlemobileapp", + "addon.mod_chat.errorwhilegettingchatusers": "local_moodlemobileapp", + "addon.mod_chat.errorwhileretrievingmessages": "local_moodlemobileapp", + "addon.mod_chat.errorwhilesendingmessage": "local_moodlemobileapp", + "addon.mod_chat.messagebeepseveryone": "chat", + "addon.mod_chat.messagebeepsyou": "chat", + "addon.mod_chat.messageenter": "chat", + "addon.mod_chat.messageexit": "chat", + "addon.mod_chat.messages": "chat", + "addon.mod_chat.messageyoubeep": "chat", + "addon.mod_chat.modulenameplural": "chat", + "addon.mod_chat.mustbeonlinetosendmessages": "local_moodlemobileapp", + "addon.mod_chat.nomessages": "chat", + "addon.mod_chat.nosessionsfound": "local_moodlemobileapp", + "addon.mod_chat.saidto": "chat", + "addon.mod_chat.send": "chat", + "addon.mod_chat.sessionstart": "chat", + "addon.mod_chat.showincompletesessions": "local_moodlemobileapp", + "addon.mod_chat.talk": "chat", + "addon.mod_chat.viewreport": "chat", + "addon.mod_choice.cannotsubmit": "choice", + "addon.mod_choice.choiceoptions": "choice", + "addon.mod_choice.errorgetchoice": "local_moodlemobileapp", + "addon.mod_choice.expired": "choice", + "addon.mod_choice.full": "choice", + "addon.mod_choice.limita": "choice", + "addon.mod_choice.modulenameplural": "choice", + "addon.mod_choice.noresultsviewable": "choice", + "addon.mod_choice.notopenyet": "choice", + "addon.mod_choice.numberofuser": "choice", + "addon.mod_choice.numberofuserinpercentage": "choice", + "addon.mod_choice.previewonly": "choice", + "addon.mod_choice.publishinfoanonafter": "choice", + "addon.mod_choice.publishinfoanonclose": "choice", + "addon.mod_choice.publishinfofullafter": "choice", + "addon.mod_choice.publishinfofullclose": "choice", + "addon.mod_choice.publishinfonever": "choice", + "addon.mod_choice.removemychoice": "choice", + "addon.mod_choice.responses": "choice", + "addon.mod_choice.responsesa": "choice", + "addon.mod_choice.responsesresultgraphdescription": "local_moodlemobileapp", + "addon.mod_choice.responsesresultgraphheader": "choice", + "addon.mod_choice.resultsnotsynced": "local_moodlemobileapp", + "addon.mod_choice.savemychoice": "choice", + "addon.mod_choice.userchoosethisoption": "choice", + "addon.mod_choice.yourselection": "choice", + "addon.mod_data.addentries": "data", + "addon.mod_data.advancedsearch": "data", + "addon.mod_data.alttext": "data", + "addon.mod_data.approve": "data", + "addon.mod_data.approved": "data", + "addon.mod_data.ascending": "data", + "addon.mod_data.authorfirstname": "data", + "addon.mod_data.authorlastname": "data", + "addon.mod_data.confirmdeleterecord": "data", + "addon.mod_data.descending": "data", + "addon.mod_data.disapprove": "data", + "addon.mod_data.edittagsnotsupported": "local_moodlemobileapp", + "addon.mod_data.emptyaddform": "data", + "addon.mod_data.entrieslefttoadd": "data", + "addon.mod_data.entrieslefttoaddtoview": "data", + "addon.mod_data.errorapproving": "local_moodlemobileapp", + "addon.mod_data.errordeleting": "local_moodlemobileapp", + "addon.mod_data.errormustsupplyvalue": "data", + "addon.mod_data.expired": "data", + "addon.mod_data.fields": "data", + "addon.mod_data.foundrecords": "data", + "addon.mod_data.gettinglocation": "local_moodlemobileapp", + "addon.mod_data.latlongboth": "data", + "addon.mod_data.locationnotenabled": "local_moodlemobileapp", + "addon.mod_data.locationpermissiondenied": "local_moodlemobileapp", + "addon.mod_data.menuchoose": "data", + "addon.mod_data.modulenameplural": "data", + "addon.mod_data.more": "data", + "addon.mod_data.mylocation": "local_moodlemobileapp", + "addon.mod_data.noaccess": "data", + "addon.mod_data.nomatch": "data", + "addon.mod_data.norecords": "data", + "addon.mod_data.notapproved": "data", + "addon.mod_data.notopenyet": "data", + "addon.mod_data.numrecords": "data", + "addon.mod_data.other": "data", + "addon.mod_data.recordapproved": "data", + "addon.mod_data.recorddeleted": "data", + "addon.mod_data.recorddisapproved": "data", + "addon.mod_data.resetsettings": "data", + "addon.mod_data.search": "data", + "addon.mod_data.searchbytagsnotsupported": "local_moodlemobileapp", + "addon.mod_data.selectedrequired": "data", + "addon.mod_data.single": "data", + "addon.mod_data.tagarea_data_records": "data", + "addon.mod_data.timeadded": "data", + "addon.mod_data.timemodified": "data", + "addon.mod_data.usedate": "data", + "addon.mod_feedback.analysis": "feedback", + "addon.mod_feedback.anonymous": "feedback", + "addon.mod_feedback.anonymous_entries": "feedback", + "addon.mod_feedback.average": "feedback", + "addon.mod_feedback.captchaofflinewarning": "local_moodlemobileapp", + "addon.mod_feedback.complete_the_form": "feedback", + "addon.mod_feedback.completed_feedbacks": "feedback", + "addon.mod_feedback.continue_the_form": "feedback", + "addon.mod_feedback.feedback_is_not_open": "feedback", + "addon.mod_feedback.feedback_submitted_offline": "local_moodlemobileapp", + "addon.mod_feedback.feedbackclose": "feedback", + "addon.mod_feedback.feedbackopen": "feedback", + "addon.mod_feedback.mapcourses": "feedback", + "addon.mod_feedback.maximal": "feedback", + "addon.mod_feedback.minimal": "feedback", + "addon.mod_feedback.mode": "feedback", + "addon.mod_feedback.modulenameplural": "feedback", + "addon.mod_feedback.next_page": "feedback", + "addon.mod_feedback.non_anonymous": "feedback", + "addon.mod_feedback.non_anonymous_entries": "feedback", + "addon.mod_feedback.non_respondents_students": "feedback", + "addon.mod_feedback.not_selected": "feedback", + "addon.mod_feedback.not_started": "feedback", + "addon.mod_feedback.numberoutofrange": "feedback", + "addon.mod_feedback.overview": "feedback", + "addon.mod_feedback.page_after_submit": "feedback", + "addon.mod_feedback.preview": "moodle", + "addon.mod_feedback.previous_page": "feedback", + "addon.mod_feedback.questions": "feedback", + "addon.mod_feedback.response_nr": "feedback", + "addon.mod_feedback.responses": "feedback", + "addon.mod_feedback.save_entries": "feedback", + "addon.mod_feedback.show_entries": "feedback", + "addon.mod_feedback.show_nonrespondents": "feedback", + "addon.mod_feedback.started": "feedback", + "addon.mod_feedback.this_feedback_is_already_submitted": "feedback", + "addon.mod_folder.emptyfilelist": "local_moodlemobileapp", + "addon.mod_folder.modulenameplural": "folder", + "addon.mod_forum.addanewdiscussion": "forum", + "addon.mod_forum.addanewquestion": "forum", + "addon.mod_forum.addanewtopic": "forum", + "addon.mod_forum.addtofavourites": "forum", + "addon.mod_forum.advanced": "moodle", + "addon.mod_forum.cannotadddiscussion": "forum", + "addon.mod_forum.cannotadddiscussionall": "forum", + "addon.mod_forum.cannotcreatediscussion": "forum", + "addon.mod_forum.couldnotadd": "forum", + "addon.mod_forum.couldnotupdate": "forum", + "addon.mod_forum.cutoffdatereached": "forum", + "addon.mod_forum.delete": "forum", + "addon.mod_forum.deletedpost": "forum", + "addon.mod_forum.deletesure": "forum", + "addon.mod_forum.discussion": "forum", + "addon.mod_forum.discussionlistsortbycreatedasc": "forum", + "addon.mod_forum.discussionlistsortbycreateddesc": "forum", + "addon.mod_forum.discussionlistsortbylastpostasc": "forum", + "addon.mod_forum.discussionlistsortbylastpostdesc": "forum", + "addon.mod_forum.discussionlistsortbyrepliesasc": "forum", + "addon.mod_forum.discussionlistsortbyrepliesdesc": "forum", + "addon.mod_forum.discussionlocked": "forum", + "addon.mod_forum.discussionpinned": "forum", + "addon.mod_forum.discussionsubscription": "forum", + "addon.mod_forum.edit": "forum", + "addon.mod_forum.erroremptymessage": "forum", + "addon.mod_forum.erroremptysubject": "forum", + "addon.mod_forum.errorgetforum": "local_moodlemobileapp", + "addon.mod_forum.errorgetgroups": "local_moodlemobileapp", + "addon.mod_forum.errorposttoallgroups": "local_moodlemobileapp", + "addon.mod_forum.favouriteupdated": "forum", + "addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp", + "addon.mod_forum.group": "local_moodlemobileapp", + "addon.mod_forum.lastpost": "forum", + "addon.mod_forum.lockdiscussion": "forum", + "addon.mod_forum.lockupdated": "forum", + "addon.mod_forum.message": "forum", + "addon.mod_forum.modeflatnewestfirst": "forum", + "addon.mod_forum.modeflatoldestfirst": "forum", + "addon.mod_forum.modenested": "forum", + "addon.mod_forum.modulenameplural": "forum", + "addon.mod_forum.numdiscussions": "local_moodlemobileapp", + "addon.mod_forum.numreplies": "local_moodlemobileapp", + "addon.mod_forum.pindiscussion": "forum", + "addon.mod_forum.pinupdated": "forum", + "addon.mod_forum.postisprivatereply": "forum", + "addon.mod_forum.posttoforum": "forum", + "addon.mod_forum.posttomygroups": "forum", + "addon.mod_forum.privatereply": "forum", + "addon.mod_forum.re": "forum", + "addon.mod_forum.refreshdiscussions": "local_moodlemobileapp", + "addon.mod_forum.refreshposts": "local_moodlemobileapp", + "addon.mod_forum.removefromfavourites": "forum", + "addon.mod_forum.reply": "forum", + "addon.mod_forum.replyplaceholder": "forum", + "addon.mod_forum.subject": "forum", + "addon.mod_forum.tagarea_forum_posts": "forum", + "addon.mod_forum.thisforumhasduedate": "forum", + "addon.mod_forum.thisforumisdue": "forum", + "addon.mod_forum.unlockdiscussion": "forum", + "addon.mod_forum.unpindiscussion": "forum", + "addon.mod_forum.unread": "forum", + "addon.mod_forum.unreadpostsnumber": "forum", + "addon.mod_forum.yourreply": "forum", + "addon.mod_glossary.addentry": "glossary", + "addon.mod_glossary.aliases": "glossary", + "addon.mod_glossary.attachment": "glossary", + "addon.mod_glossary.browsemode": "local_moodlemobileapp", + "addon.mod_glossary.byalphabet": "local_moodlemobileapp", + "addon.mod_glossary.byauthor": "local_moodlemobileapp", + "addon.mod_glossary.bycategory": "local_moodlemobileapp", + "addon.mod_glossary.bynewestfirst": "local_moodlemobileapp", + "addon.mod_glossary.byrecentlyupdated": "local_moodlemobileapp", + "addon.mod_glossary.bysearch": "local_moodlemobileapp", + "addon.mod_glossary.cannoteditentry": "local_moodlemobileapp", + "addon.mod_glossary.casesensitive": "glossary", + "addon.mod_glossary.categories": "glossary", + "addon.mod_glossary.concept": "glossary", + "addon.mod_glossary.definition": "glossary", + "addon.mod_glossary.entriestobesynced": "local_moodlemobileapp", + "addon.mod_glossary.entrypendingapproval": "local_moodlemobileapp", + "addon.mod_glossary.entryusedynalink": "glossary", + "addon.mod_glossary.errconceptalreadyexists": "glossary", + "addon.mod_glossary.errorloadingentries": "local_moodlemobileapp", + "addon.mod_glossary.errorloadingentry": "local_moodlemobileapp", + "addon.mod_glossary.errorloadingglossary": "local_moodlemobileapp", + "addon.mod_glossary.fillfields": "glossary", + "addon.mod_glossary.fullmatch": "glossary", + "addon.mod_glossary.linking": "glossary", + "addon.mod_glossary.modulenameplural": "glossary", + "addon.mod_glossary.noentriesfound": "local_moodlemobileapp", + "addon.mod_glossary.searchquery": "local_moodlemobileapp", + "addon.mod_glossary.tagarea_glossary_entries": "glossary", + "addon.mod_h5pactivity.all_attempts": "h5pactivity", + "addon.mod_h5pactivity.answer_checked": "h5pactivity", + "addon.mod_h5pactivity.answer_correct": "h5pactivity", + "addon.mod_h5pactivity.answer_fail": "h5pactivity", + "addon.mod_h5pactivity.answer_incorrect": "h5pactivity", + "addon.mod_h5pactivity.answer_pass": "h5pactivity", + "addon.mod_h5pactivity.attempt": "h5pactivity", + "addon.mod_h5pactivity.attempt_completion_no": "h5pactivity", + "addon.mod_h5pactivity.attempt_completion_yes": "h5pactivity", + "addon.mod_h5pactivity.attempt_success_fail": "h5pactivity", + "addon.mod_h5pactivity.attempt_success_pass": "h5pactivity", + "addon.mod_h5pactivity.attempt_success_unknown": "h5pactivity", + "addon.mod_h5pactivity.attempts_none": "h5pactivity", + "addon.mod_h5pactivity.completion": "h5pactivity", + "addon.mod_h5pactivity.downloadh5pfile": "local_moodlemobileapp", + "addon.mod_h5pactivity.duration": "h5pactivity", + "addon.mod_h5pactivity.errorgetactivity": "local_moodlemobileapp", + "addon.mod_h5pactivity.filestatenotdownloaded": "local_moodlemobileapp", + "addon.mod_h5pactivity.filestateoutdated": "local_moodlemobileapp", + "addon.mod_h5pactivity.maxscore": "h5pactivity", + "addon.mod_h5pactivity.modulenameplural": "h5pactivity", + "addon.mod_h5pactivity.myattempts": "h5pactivity", + "addon.mod_h5pactivity.no_compatible_track": "h5pactivity", + "addon.mod_h5pactivity.offlinedisabledwarning": "local_moodlemobileapp", + "addon.mod_h5pactivity.outcome": "h5pactivity", + "addon.mod_h5pactivity.previewmode": "h5pactivity", + "addon.mod_h5pactivity.result_fill-in": "h5pactivity", + "addon.mod_h5pactivity.result_other": "h5pactivity", + "addon.mod_h5pactivity.review_my_attempts": "h5pactivity", + "addon.mod_h5pactivity.score": "h5pactivity", + "addon.mod_h5pactivity.score_out_of": "h5pactivity", + "addon.mod_h5pactivity.startdate": "h5pactivity", + "addon.mod_h5pactivity.totalscore": "h5pactivity", + "addon.mod_h5pactivity.viewattempt": "local_moodlemobileapp", + "addon.mod_imscp.deploymenterror": "imscp", + "addon.mod_imscp.modulenameplural": "imscp", + "addon.mod_imscp.showmoduledescription": "local_moodlemobileapp", + "addon.mod_imscp.toc": "imscp", + "addon.mod_lesson.answer": "lesson", + "addon.mod_lesson.attempt": "lesson", + "addon.mod_lesson.attemptheader": "lesson", + "addon.mod_lesson.attemptsremaining": "lesson", + "addon.mod_lesson.averagescore": "lesson", + "addon.mod_lesson.averagetime": "lesson", + "addon.mod_lesson.branchtable": "lesson", + "addon.mod_lesson.cannotfindattempt": "lesson", + "addon.mod_lesson.cannotfinduser": "lesson", + "addon.mod_lesson.clusterjump": "lesson", + "addon.mod_lesson.completed": "lesson", + "addon.mod_lesson.congratulations": "lesson", + "addon.mod_lesson.continue": "lesson", + "addon.mod_lesson.continuetonextpage": "lesson", + "addon.mod_lesson.defaultessayresponse": "lesson", + "addon.mod_lesson.detailedstats": "lesson", + "addon.mod_lesson.didnotanswerquestion": "lesson", + "addon.mod_lesson.displayofgrade": "lesson", + "addon.mod_lesson.displayscorewithessays": "lesson", + "addon.mod_lesson.displayscorewithoutessays": "lesson", + "addon.mod_lesson.emptypassword": "lesson", + "addon.mod_lesson.enterpassword": "lesson", + "addon.mod_lesson.eolstudentoutoftimenoanswers": "lesson", + "addon.mod_lesson.errorprefetchrandombranch": "local_moodlemobileapp", + "addon.mod_lesson.errorreviewretakenotlast": "local_moodlemobileapp", + "addon.mod_lesson.finish": "lesson", + "addon.mod_lesson.finishretakeoffline": "local_moodlemobileapp", + "addon.mod_lesson.firstwrong": "lesson", + "addon.mod_lesson.gotoendoflesson": "lesson", + "addon.mod_lesson.grade": "lesson", + "addon.mod_lesson.highscore": "lesson", + "addon.mod_lesson.hightime": "lesson", + "addon.mod_lesson.leftduringtimed": "lesson", + "addon.mod_lesson.leftduringtimednoretake": "lesson", + "addon.mod_lesson.lessonmenu": "lesson", + "addon.mod_lesson.lessonstats": "lesson", + "addon.mod_lesson.linkedmedia": "lesson", + "addon.mod_lesson.loginfail": "lesson", + "addon.mod_lesson.lowscore": "lesson", + "addon.mod_lesson.lowtime": "lesson", + "addon.mod_lesson.maximumnumberofattemptsreached": "lesson", + "addon.mod_lesson.modattemptsnoteacher": "lesson", + "addon.mod_lesson.modulenameplural": "lesson", + "addon.mod_lesson.noanswer": "lesson", + "addon.mod_lesson.nolessonattempts": "lesson", + "addon.mod_lesson.nolessonattemptsgroup": "lesson", + "addon.mod_lesson.notcompleted": "lesson", + "addon.mod_lesson.numberofcorrectanswers": "lesson", + "addon.mod_lesson.numberofpagesviewed": "lesson", + "addon.mod_lesson.numberofpagesviewednotice": "lesson", + "addon.mod_lesson.ongoingcustom": "lesson", + "addon.mod_lesson.ongoingnormal": "lesson", + "addon.mod_lesson.or": "lesson", + "addon.mod_lesson.overview": "lesson", + "addon.mod_lesson.preview": "lesson", + "addon.mod_lesson.progressbarteacherwarning2": "lesson", + "addon.mod_lesson.progresscompleted": "lesson", + "addon.mod_lesson.question": "lesson", + "addon.mod_lesson.rawgrade": "lesson", + "addon.mod_lesson.reports": "lesson", + "addon.mod_lesson.response": "lesson", + "addon.mod_lesson.retakefinishedinsync": "local_moodlemobileapp", + "addon.mod_lesson.retakelabelfull": "local_moodlemobileapp", + "addon.mod_lesson.retakelabelshort": "local_moodlemobileapp", + "addon.mod_lesson.review": "lesson", + "addon.mod_lesson.reviewlesson": "lesson", + "addon.mod_lesson.reviewquestionback": "lesson", + "addon.mod_lesson.reviewquestioncontinue": "lesson", + "addon.mod_lesson.secondpluswrong": "lesson", + "addon.mod_lesson.submit": "lesson", + "addon.mod_lesson.teacherjumpwarning": "lesson", + "addon.mod_lesson.teacherongoingwarning": "lesson", + "addon.mod_lesson.teachertimerwarning": "lesson", + "addon.mod_lesson.thatsthecorrectanswer": "lesson", + "addon.mod_lesson.thatsthewronganswer": "lesson", + "addon.mod_lesson.timeremaining": "lesson", + "addon.mod_lesson.timetaken": "lesson", + "addon.mod_lesson.unseenpageinbranch": "lesson", + "addon.mod_lesson.warningretakefinished": "local_moodlemobileapp", + "addon.mod_lesson.welldone": "lesson", + "addon.mod_lesson.youhaveseen": "lesson", + "addon.mod_lesson.youranswer": "lesson", + "addon.mod_lesson.yourcurrentgradeisoutof": "lesson", + "addon.mod_lesson.youshouldview": "lesson", + "addon.mod_lti.errorgetlti": "local_moodlemobileapp", + "addon.mod_lti.errorinvalidlaunchurl": "local_moodlemobileapp", + "addon.mod_lti.launchactivity": "local_moodlemobileapp", + "addon.mod_lti.modulenameplural": "lti", + "addon.mod_page.errorwhileloadingthepage": "local_moodlemobileapp", + "addon.mod_page.modulenameplural": "page", + "addon.mod_quiz.answercolon": "qtype_numerical", + "addon.mod_quiz.attemptfirst": "quiz", + "addon.mod_quiz.attemptlast": "quiz", + "addon.mod_quiz.attemptnumber": "quiz", + "addon.mod_quiz.attemptquiznow": "quiz", + "addon.mod_quiz.attemptstate": "quiz", + "addon.mod_quiz.canattemptbutnotsubmit": "local_moodlemobileapp", + "addon.mod_quiz.cannotsubmitquizdueto": "local_moodlemobileapp", + "addon.mod_quiz.clearchoice": "qtype_multichoice", + "addon.mod_quiz.comment": "quiz", + "addon.mod_quiz.completedon": "quiz", + "addon.mod_quiz.confirmclose": "quiz", + "addon.mod_quiz.confirmcontinueoffline": "local_moodlemobileapp", + "addon.mod_quiz.confirmleavequizonerror": "local_moodlemobileapp", + "addon.mod_quiz.confirmstart": "quizaccess_timelimit", + "addon.mod_quiz.confirmstartheader": "quizaccess_timelimit", + "addon.mod_quiz.connectionerror": "quiz", + "addon.mod_quiz.continueattemptquiz": "quiz", + "addon.mod_quiz.continuepreview": "quiz", + "addon.mod_quiz.errorbehaviournotsupported": "local_moodlemobileapp", + "addon.mod_quiz.errordownloading": "local_moodlemobileapp", + "addon.mod_quiz.errorgetattempt": "local_moodlemobileapp", + "addon.mod_quiz.errorgetquestions": "local_moodlemobileapp", + "addon.mod_quiz.errorgetquiz": "local_moodlemobileapp", + "addon.mod_quiz.errorparsequestions": "local_moodlemobileapp", + "addon.mod_quiz.errorquestionsnotsupported": "local_moodlemobileapp", + "addon.mod_quiz.errorrulesnotsupported": "local_moodlemobileapp", + "addon.mod_quiz.errorsaveattempt": "local_moodlemobileapp", + "addon.mod_quiz.feedback": "quiz", + "addon.mod_quiz.finishattemptdots": "quiz", + "addon.mod_quiz.finishnotsynced": "local_moodlemobileapp", + "addon.mod_quiz.grade": "quiz", + "addon.mod_quiz.gradeaverage": "quiz", + "addon.mod_quiz.gradehighest": "quiz", + "addon.mod_quiz.grademethod": "quiz", + "addon.mod_quiz.gradesofar": "quiz", + "addon.mod_quiz.marks": "quiz", + "addon.mod_quiz.modulenameplural": "quiz", + "addon.mod_quiz.mustbesubmittedby": "quiz", + "addon.mod_quiz.noquestions": "quiz", + "addon.mod_quiz.noreviewattempt": "quiz", + "addon.mod_quiz.notyetgraded": "quiz", + "addon.mod_quiz.opentoc": "local_moodlemobileapp", + "addon.mod_quiz.outof": "quiz", + "addon.mod_quiz.outofpercent": "quiz", + "addon.mod_quiz.outofshort": "quiz", + "addon.mod_quiz.overallfeedback": "quiz", + "addon.mod_quiz.overdue": "quiz", + "addon.mod_quiz.overduemustbesubmittedby": "quiz", + "addon.mod_quiz.preview": "quiz", + "addon.mod_quiz.previewquiznow": "quiz", + "addon.mod_quiz.question": "quiz", + "addon.mod_quiz.quiznavigation": "quiz", + "addon.mod_quiz.quizpassword": "quizaccess_password", + "addon.mod_quiz.reattemptquiz": "quiz", + "addon.mod_quiz.requirepasswordmessage": "quizaccess_password", + "addon.mod_quiz.returnattempt": "quiz", + "addon.mod_quiz.review": "quiz", + "addon.mod_quiz.reviewofattempt": "quiz", + "addon.mod_quiz.reviewofpreview": "quiz", + "addon.mod_quiz.showall": "quiz", + "addon.mod_quiz.showeachpage": "quiz", + "addon.mod_quiz.startattempt": "quiz", + "addon.mod_quiz.startedon": "quiz", + "addon.mod_quiz.stateabandoned": "quiz", + "addon.mod_quiz.statefinished": "quiz", + "addon.mod_quiz.statefinisheddetails": "quiz", + "addon.mod_quiz.stateinprogress": "quiz", + "addon.mod_quiz.stateoverdue": "quiz", + "addon.mod_quiz.stateoverduedetails": "quiz", + "addon.mod_quiz.status": "quiz", + "addon.mod_quiz.submitallandfinish": "quiz", + "addon.mod_quiz.summaryofattempt": "quiz", + "addon.mod_quiz.summaryofattempts": "quiz", + "addon.mod_quiz.timeleft": "quiz", + "addon.mod_quiz.timetaken": "quiz", + "addon.mod_quiz.warningattemptfinished": "local_moodlemobileapp", + "addon.mod_quiz.warningdatadiscarded": "local_moodlemobileapp", + "addon.mod_quiz.warningdatadiscardedfromfinished": "local_moodlemobileapp", + "addon.mod_quiz.warningquestionsnotsupported": "local_moodlemobileapp", + "addon.mod_quiz.yourfinalgradeis": "quiz", + "addon.mod_resource.errorwhileloadingthecontent": "local_moodlemobileapp", + "addon.mod_resource.modifieddate": "resource", + "addon.mod_resource.modulenameplural": "resource", + "addon.mod_resource.openthefile": "local_moodlemobileapp", + "addon.mod_resource.uploadeddate": "resource", + "addon.mod_scorm.asset": "scorm", + "addon.mod_scorm.assetlaunched": "scorm", + "addon.mod_scorm.attempts": "scorm", + "addon.mod_scorm.averageattempt": "scorm", + "addon.mod_scorm.browse": "scorm", + "addon.mod_scorm.browsed": "scorm", + "addon.mod_scorm.browsemode": "scorm", + "addon.mod_scorm.cannotcalculategrade": "local_moodlemobileapp", + "addon.mod_scorm.completed": "scorm", + "addon.mod_scorm.contents": "scorm", + "addon.mod_scorm.dataattemptshown": "local_moodlemobileapp", + "addon.mod_scorm.enter": "scorm", + "addon.mod_scorm.errorcreateofflineattempt": "local_moodlemobileapp", + "addon.mod_scorm.errordownloadscorm": "local_moodlemobileapp", + "addon.mod_scorm.errorgetscorm": "local_moodlemobileapp", + "addon.mod_scorm.errorinvalidversion": "local_moodlemobileapp", + "addon.mod_scorm.errornotdownloadable": "local_moodlemobileapp", + "addon.mod_scorm.errornovalidsco": "local_moodlemobileapp", + "addon.mod_scorm.errorpackagefile": "local_moodlemobileapp", + "addon.mod_scorm.errorsyncscorm": "local_moodlemobileapp", + "addon.mod_scorm.exceededmaxattempts": "scorm", + "addon.mod_scorm.failed": "scorm", + "addon.mod_scorm.firstattempt": "scorm", + "addon.mod_scorm.gradeaverage": "scorm", + "addon.mod_scorm.gradeforattempt": "scorm", + "addon.mod_scorm.gradehighest": "scorm", + "addon.mod_scorm.grademethod": "scorm", + "addon.mod_scorm.gradereported": "scorm", + "addon.mod_scorm.gradescoes": "scorm", + "addon.mod_scorm.gradesum": "scorm", + "addon.mod_scorm.highestattempt": "scorm", + "addon.mod_scorm.incomplete": "scorm", + "addon.mod_scorm.lastattempt": "scorm", + "addon.mod_scorm.modulenameplural": "scorm", + "addon.mod_scorm.newattempt": "scorm", + "addon.mod_scorm.noattemptsallowed": "scorm", + "addon.mod_scorm.noattemptsmade": "scorm", + "addon.mod_scorm.notattempted": "scorm", + "addon.mod_scorm.offlineattemptnote": "local_moodlemobileapp", + "addon.mod_scorm.offlineattemptovermax": "local_moodlemobileapp", + "addon.mod_scorm.organizations": "scorm", + "addon.mod_scorm.passed": "scorm", + "addon.mod_scorm.reviewmode": "scorm", + "addon.mod_scorm.score": "scorm", + "addon.mod_scorm.scormstatusnotdownloaded": "local_moodlemobileapp", + "addon.mod_scorm.scormstatusoutdated": "local_moodlemobileapp", + "addon.mod_scorm.suspended": "scorm", + "addon.mod_scorm.toc": "scorm", + "addon.mod_scorm.warningofflinedatadeleted": "local_moodlemobileapp", + "addon.mod_scorm.warningsynconlineincomplete": "local_moodlemobileapp", + "addon.mod_survey.cannotsubmitsurvey": "local_moodlemobileapp", + "addon.mod_survey.errorgetsurvey": "local_moodlemobileapp", + "addon.mod_survey.ifoundthat": "survey", + "addon.mod_survey.ipreferthat": "survey", + "addon.mod_survey.modulenameplural": "survey", + "addon.mod_survey.responses": "survey", + "addon.mod_survey.results": "local_moodlemobileapp", + "addon.mod_survey.surveycompletednograph": "survey", + "addon.mod_url.accessurl": "local_moodlemobileapp", + "addon.mod_url.modulenameplural": "url", + "addon.mod_url.pointingtourl": "local_moodlemobileapp", + "addon.mod_wiki.cannoteditpage": "wiki", + "addon.mod_wiki.createpage": "wiki", + "addon.mod_wiki.editingpage": "wiki", + "addon.mod_wiki.errorloadingpage": "local_moodlemobileapp", + "addon.mod_wiki.errornowikiavailable": "local_moodlemobileapp", + "addon.mod_wiki.gowikihome": "local_moodlemobileapp", + "addon.mod_wiki.map": "wiki", + "addon.mod_wiki.modulenameplural": "wiki", + "addon.mod_wiki.newpagehdr": "wiki", + "addon.mod_wiki.newpagetitle": "wiki", + "addon.mod_wiki.nocontent": "wiki", + "addon.mod_wiki.notingroup": "wiki", + "addon.mod_wiki.pageexists": "wiki", + "addon.mod_wiki.pagename": "wiki", + "addon.mod_wiki.subwiki": "local_moodlemobileapp", + "addon.mod_wiki.tagarea_wiki_pages": "wiki", + "addon.mod_wiki.titleshouldnotbeempty": "local_moodlemobileapp", + "addon.mod_wiki.viewpage": "local_moodlemobileapp", + "addon.mod_wiki.wikipage": "local_moodlemobileapp", + "addon.mod_wiki.wrongversionlock": "wiki", + "addon.mod_workshop.alreadygraded": "workshop", + "addon.mod_workshop.areainstructauthors": "workshop", + "addon.mod_workshop.areainstructreviewers": "workshop", + "addon.mod_workshop.assess": "workshop", + "addon.mod_workshop.assessedsubmission": "workshop", + "addon.mod_workshop.assessmentform": "workshop", + "addon.mod_workshop.assessmentsettings": "workshop", + "addon.mod_workshop.assessmentstrategynotsupported": "local_moodlemobileapp", + "addon.mod_workshop.assessmentweight": "workshop", + "addon.mod_workshop.assignedassessments": "workshop", + "addon.mod_workshop.assignedassessmentsnone": "workshop", + "addon.mod_workshop.conclusion": "workshop", + "addon.mod_workshop.createsubmission": "workshop", + "addon.mod_workshop.deletesubmission": "workshop", + "addon.mod_workshop.editsubmission": "workshop", + "addon.mod_workshop.feedbackauthor": "workshop", + "addon.mod_workshop.feedbackby": "workshop", + "addon.mod_workshop.feedbackreviewer": "workshop", + "addon.mod_workshop.givengrades": "workshop", + "addon.mod_workshop.gradecalculated": "workshop", + "addon.mod_workshop.gradeinfo": "workshop", + "addon.mod_workshop.gradeover": "workshop", + "addon.mod_workshop.gradesreport": "workshop", + "addon.mod_workshop.gradinggrade": "workshop", + "addon.mod_workshop.gradinggradecalculated": "workshop", + "addon.mod_workshop.gradinggradeof": "workshop", + "addon.mod_workshop.gradinggradeover": "workshop", + "addon.mod_workshop.modulenameplural": "workshop", + "addon.mod_workshop.nogradeyet": "workshop", + "addon.mod_workshop.notassessed": "workshop", + "addon.mod_workshop.notoverridden": "workshop", + "addon.mod_workshop.noyoursubmission": "workshop", + "addon.mod_workshop.overallfeedback": "workshop", + "addon.mod_workshop.publishedsubmissions": "workshop", + "addon.mod_workshop.publishsubmission": "workshop", + "addon.mod_workshop.publishsubmission_help": "workshop", + "addon.mod_workshop.reassess": "workshop", + "addon.mod_workshop.receivedgrades": "workshop", + "addon.mod_workshop.submissionattachment": "workshop", + "addon.mod_workshop.submissioncontent": "workshop", + "addon.mod_workshop.submissiondeleteconfirm": "workshop", + "addon.mod_workshop.submissiongrade": "workshop", + "addon.mod_workshop.submissiongradeof": "workshop", + "addon.mod_workshop.submissionrequiredcontent": "workshop", + "addon.mod_workshop.submissionrequiredtitle": "local_moodlemobileapp", + "addon.mod_workshop.submissionsreport": "workshop", + "addon.mod_workshop.submissiontitle": "workshop", + "addon.mod_workshop.switchphase10": "workshop", + "addon.mod_workshop.switchphase20": "workshop", + "addon.mod_workshop.switchphase30": "workshop", + "addon.mod_workshop.switchphase40": "workshop", + "addon.mod_workshop.switchphase50": "workshop", + "addon.mod_workshop.userplan": "workshop", + "addon.mod_workshop.userplancurrentphase": "workshop", + "addon.mod_workshop.warningassessmentmodified": "local_moodlemobileapp", + "addon.mod_workshop.warningsubmissionmodified": "local_moodlemobileapp", + "addon.mod_workshop.weightinfo": "workshop", + "addon.mod_workshop.yourassessment": "workshop/assessmentbyyourself", + "addon.mod_workshop.yourassessmentfor": "workshop", + "addon.mod_workshop.yourgrades": "workshop", + "addon.mod_workshop.yoursubmission": "workshop", + "addon.mod_workshop_assessment_accumulative.dimensioncommentfor": "workshopform_accumulative", + "addon.mod_workshop_assessment_accumulative.dimensiongradefor": "workshopform_accumulative", + "addon.mod_workshop_assessment_accumulative.dimensionnumber": "workshopform_accumulative", + "addon.mod_workshop_assessment_accumulative.mustchoosegrade": "workshopform_accumulative", + "addon.mod_workshop_assessment_comments.dimensioncommentfor": "workshopform_comments", + "addon.mod_workshop_assessment_comments.dimensionnumber": "workshopform_comments", + "addon.mod_workshop_assessment_numerrors.dimensioncommentfor": "workshopform_numerrors", + "addon.mod_workshop_assessment_numerrors.dimensiongradefor": "workshopform_accumulative", + "addon.mod_workshop_assessment_numerrors.dimensionnumber": "workshopform_numerrors", + "addon.mod_workshop_assessment_rubric.dimensionnumber": "workshopform_rubric", + "addon.mod_workshop_assessment_rubric.mustchooseone": "workshopform_rubric", + "addon.notes.addnewnote": "notes", + "addon.notes.coursenotes": "notes", + "addon.notes.deleteconfirm": "notes", + "addon.notes.eventnotecreated": "notes", + "addon.notes.eventnotedeleted": "notes", + "addon.notes.nonotes": "notes", + "addon.notes.note": "notes", + "addon.notes.notes": "notes", + "addon.notes.personalnotes": "notes", + "addon.notes.publishstate": "notes", + "addon.notes.sitenotes": "notes", + "addon.notes.userwithid": "local_moodlemobileapp", + "addon.notes.warningnotenotsent": "local_moodlemobileapp", + "addon.notifications.errorgetnotifications": "local_moodlemobileapp", + "addon.notifications.markallread": "moodle", + "addon.notifications.notificationpreferences": "message", + "addon.notifications.notifications": "local_moodlemobileapp", + "addon.notifications.playsound": "local_moodlemobileapp", + "addon.notifications.therearentnotificationsyet": "local_moodlemobileapp", + "addon.storagemanager.deletecourse": "local_moodlemobileapp", + "addon.storagemanager.deletecourses": "local_moodlemobileapp", + "addon.storagemanager.deletedatafrom": "local_moodlemobileapp", + "addon.storagemanager.info": "local_moodlemobileapp", + "addon.storagemanager.managestorage": "local_moodlemobileapp", + "addon.storagemanager.storageused": "local_moodlemobileapp", + "assets.countries.AD": "countries", + "assets.countries.AE": "countries", + "assets.countries.AF": "countries", + "assets.countries.AG": "countries", + "assets.countries.AI": "countries", + "assets.countries.AL": "countries", + "assets.countries.AM": "countries", + "assets.countries.AO": "countries", + "assets.countries.AQ": "countries", + "assets.countries.AR": "countries", + "assets.countries.AS": "countries", + "assets.countries.AT": "countries", + "assets.countries.AU": "countries", + "assets.countries.AW": "countries", + "assets.countries.AX": "countries", + "assets.countries.AZ": "countries", + "assets.countries.BA": "countries", + "assets.countries.BB": "countries", + "assets.countries.BD": "countries", + "assets.countries.BE": "countries", + "assets.countries.BF": "countries", + "assets.countries.BG": "countries", + "assets.countries.BH": "countries", + "assets.countries.BI": "countries", + "assets.countries.BJ": "countries", + "assets.countries.BL": "countries", + "assets.countries.BM": "countries", + "assets.countries.BN": "countries", + "assets.countries.BO": "countries", + "assets.countries.BQ": "countries", + "assets.countries.BR": "countries", + "assets.countries.BS": "countries", + "assets.countries.BT": "countries", + "assets.countries.BV": "countries", + "assets.countries.BW": "countries", + "assets.countries.BY": "countries", + "assets.countries.BZ": "countries", + "assets.countries.CA": "countries", + "assets.countries.CC": "countries", + "assets.countries.CD": "countries", + "assets.countries.CF": "countries", + "assets.countries.CG": "countries", + "assets.countries.CH": "countries", + "assets.countries.CI": "countries", + "assets.countries.CK": "countries", + "assets.countries.CL": "countries", + "assets.countries.CM": "countries", + "assets.countries.CN": "countries", + "assets.countries.CO": "countries", + "assets.countries.CR": "countries", + "assets.countries.CU": "countries", + "assets.countries.CV": "countries", + "assets.countries.CW": "countries", + "assets.countries.CX": "countries", + "assets.countries.CY": "countries", + "assets.countries.CZ": "countries", + "assets.countries.DE": "countries", + "assets.countries.DJ": "countries", + "assets.countries.DK": "countries", + "assets.countries.DM": "countries", + "assets.countries.DO": "countries", + "assets.countries.DZ": "countries", + "assets.countries.EC": "countries", + "assets.countries.EE": "countries", + "assets.countries.EG": "countries", + "assets.countries.EH": "countries", + "assets.countries.ER": "countries", + "assets.countries.ES": "countries", + "assets.countries.ET": "countries", + "assets.countries.FI": "countries", + "assets.countries.FJ": "countries", + "assets.countries.FK": "countries", + "assets.countries.FM": "countries", + "assets.countries.FO": "countries", + "assets.countries.FR": "countries", + "assets.countries.GA": "countries", + "assets.countries.GB": "countries", + "assets.countries.GD": "countries", + "assets.countries.GE": "countries", + "assets.countries.GF": "countries", + "assets.countries.GG": "countries", + "assets.countries.GH": "countries", + "assets.countries.GI": "countries", + "assets.countries.GL": "countries", + "assets.countries.GM": "countries", + "assets.countries.GN": "countries", + "assets.countries.GP": "countries", + "assets.countries.GQ": "countries", + "assets.countries.GR": "countries", + "assets.countries.GS": "countries", + "assets.countries.GT": "countries", + "assets.countries.GU": "countries", + "assets.countries.GW": "countries", + "assets.countries.GY": "countries", + "assets.countries.HK": "countries", + "assets.countries.HM": "countries", + "assets.countries.HN": "countries", + "assets.countries.HR": "countries", + "assets.countries.HT": "countries", + "assets.countries.HU": "countries", + "assets.countries.ID": "countries", + "assets.countries.IE": "countries", + "assets.countries.IL": "countries", + "assets.countries.IM": "countries", + "assets.countries.IN": "countries", + "assets.countries.IO": "countries", + "assets.countries.IQ": "countries", + "assets.countries.IR": "countries", + "assets.countries.IS": "countries", + "assets.countries.IT": "countries", + "assets.countries.JE": "countries", + "assets.countries.JM": "countries", + "assets.countries.JO": "countries", + "assets.countries.JP": "countries", + "assets.countries.KE": "countries", + "assets.countries.KG": "countries", + "assets.countries.KH": "countries", + "assets.countries.KI": "countries", + "assets.countries.KM": "countries", + "assets.countries.KN": "countries", + "assets.countries.KP": "countries", + "assets.countries.KR": "countries", + "assets.countries.KW": "countries", + "assets.countries.KY": "countries", + "assets.countries.KZ": "countries", + "assets.countries.LA": "countries", + "assets.countries.LB": "countries", + "assets.countries.LC": "countries", + "assets.countries.LI": "countries", + "assets.countries.LK": "countries", + "assets.countries.LR": "countries", + "assets.countries.LS": "countries", + "assets.countries.LT": "countries", + "assets.countries.LU": "countries", + "assets.countries.LV": "countries", + "assets.countries.LY": "countries", + "assets.countries.MA": "countries", + "assets.countries.MC": "countries", + "assets.countries.MD": "countries", + "assets.countries.ME": "countries", + "assets.countries.MF": "countries", + "assets.countries.MG": "countries", + "assets.countries.MH": "countries", + "assets.countries.MK": "countries", + "assets.countries.ML": "countries", + "assets.countries.MM": "countries", + "assets.countries.MN": "countries", + "assets.countries.MO": "countries", + "assets.countries.MP": "countries", + "assets.countries.MQ": "countries", + "assets.countries.MR": "countries", + "assets.countries.MS": "countries", + "assets.countries.MT": "countries", + "assets.countries.MU": "countries", + "assets.countries.MV": "countries", + "assets.countries.MW": "countries", + "assets.countries.MX": "countries", + "assets.countries.MY": "countries", + "assets.countries.MZ": "countries", + "assets.countries.NA": "countries", + "assets.countries.NC": "countries", + "assets.countries.NE": "countries", + "assets.countries.NF": "countries", + "assets.countries.NG": "countries", + "assets.countries.NI": "countries", + "assets.countries.NL": "countries", + "assets.countries.NO": "countries", + "assets.countries.NP": "countries", + "assets.countries.NR": "countries", + "assets.countries.NU": "countries", + "assets.countries.NZ": "countries", + "assets.countries.OM": "countries", + "assets.countries.PA": "countries", + "assets.countries.PE": "countries", + "assets.countries.PF": "countries", + "assets.countries.PG": "countries", + "assets.countries.PH": "countries", + "assets.countries.PK": "countries", + "assets.countries.PL": "countries", + "assets.countries.PM": "countries", + "assets.countries.PN": "countries", + "assets.countries.PR": "countries", + "assets.countries.PS": "countries", + "assets.countries.PT": "countries", + "assets.countries.PW": "countries", + "assets.countries.PY": "countries", + "assets.countries.QA": "countries", + "assets.countries.RE": "countries", + "assets.countries.RO": "countries", + "assets.countries.RS": "countries", + "assets.countries.RU": "countries", + "assets.countries.RW": "countries", + "assets.countries.SA": "countries", + "assets.countries.SB": "countries", + "assets.countries.SC": "countries", + "assets.countries.SD": "countries", + "assets.countries.SE": "countries", + "assets.countries.SG": "countries", + "assets.countries.SH": "countries", + "assets.countries.SI": "countries", + "assets.countries.SJ": "countries", + "assets.countries.SK": "countries", + "assets.countries.SL": "countries", + "assets.countries.SM": "countries", + "assets.countries.SN": "countries", + "assets.countries.SO": "countries", + "assets.countries.SR": "countries", + "assets.countries.SS": "countries", + "assets.countries.ST": "countries", + "assets.countries.SV": "countries", + "assets.countries.SX": "countries", + "assets.countries.SY": "countries", + "assets.countries.SZ": "countries", + "assets.countries.TC": "countries", + "assets.countries.TD": "countries", + "assets.countries.TF": "countries", + "assets.countries.TG": "countries", + "assets.countries.TH": "countries", + "assets.countries.TJ": "countries", + "assets.countries.TK": "countries", + "assets.countries.TL": "countries", + "assets.countries.TM": "countries", + "assets.countries.TN": "countries", + "assets.countries.TO": "countries", + "assets.countries.TR": "countries", + "assets.countries.TT": "countries", + "assets.countries.TV": "countries", + "assets.countries.TW": "countries", + "assets.countries.TZ": "countries", + "assets.countries.UA": "countries", + "assets.countries.UG": "countries", + "assets.countries.UM": "countries", + "assets.countries.US": "countries", + "assets.countries.UY": "countries", + "assets.countries.UZ": "countries", + "assets.countries.VA": "countries", + "assets.countries.VC": "countries", + "assets.countries.VE": "countries", + "assets.countries.VG": "countries", + "assets.countries.VI": "countries", + "assets.countries.VN": "countries", + "assets.countries.VU": "countries", + "assets.countries.WF": "countries", + "assets.countries.WS": "countries", + "assets.countries.YE": "countries", + "assets.countries.YT": "countries", + "assets.countries.ZA": "countries", + "assets.countries.ZM": "countries", + "assets.countries.ZW": "countries", + "assets.mimetypes.application/epub_zip": "mimetypes", + "assets.mimetypes.application/msword": "mimetypes", + "assets.mimetypes.application/pdf": "mimetypes", + "assets.mimetypes.application/vnd.moodle.backup": "mimetypes", + "assets.mimetypes.application/vnd.ms-excel": "mimetypes", + "assets.mimetypes.application/vnd.ms-excel.sheet.macroEnabled.12": "mimetypes", + "assets.mimetypes.application/vnd.ms-powerpoint": "mimetypes", + "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet": "mimetypes", + "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet-template": "mimetypes", + "assets.mimetypes.application/vnd.oasis.opendocument.text": "mimetypes", + "assets.mimetypes.application/vnd.oasis.opendocument.text-template": "mimetypes", + "assets.mimetypes.application/vnd.oasis.opendocument.text-web": "mimetypes", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "mimetypes", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "mimetypes", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "mimetypes", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "mimetypes", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "mimetypes", + "assets.mimetypes.application/x-iwork-keynote-sffkey": "mimetypes", + "assets.mimetypes.application/x-iwork-numbers-sffnumbers": "mimetypes", + "assets.mimetypes.application/x-iwork-pages-sffpages": "mimetypes", + "assets.mimetypes.application/x-javascript": "mimetypes", + "assets.mimetypes.application/x-mspublisher": "mimetypes", + "assets.mimetypes.application/x-shockwave-flash": "mimetypes", + "assets.mimetypes.application/xhtml_xml": "mimetypes", + "assets.mimetypes.archive": "mimetypes", + "assets.mimetypes.audio": "mimetypes", + "assets.mimetypes.default": "mimetypes", + "assets.mimetypes.document/unknown": "mimetypes", + "assets.mimetypes.group:archive": "mimetypes", + "assets.mimetypes.group:audio": "mimetypes", + "assets.mimetypes.group:document": "mimetypes", + "assets.mimetypes.group:html_audio": "mimetypes", + "assets.mimetypes.group:html_track": "mimetypes", + "assets.mimetypes.group:html_video": "mimetypes", + "assets.mimetypes.group:image": "mimetypes", + "assets.mimetypes.group:presentation": "mimetypes", + "assets.mimetypes.group:sourcecode": "mimetypes", + "assets.mimetypes.group:spreadsheet": "mimetypes", + "assets.mimetypes.group:video": "mimetypes", + "assets.mimetypes.group:web_audio": "mimetypes", + "assets.mimetypes.group:web_file": "mimetypes", + "assets.mimetypes.group:web_image": "mimetypes", + "assets.mimetypes.group:web_video": "mimetypes", + "assets.mimetypes.image": "mimetypes", + "assets.mimetypes.image/vnd.microsoft.icon": "mimetypes", + "assets.mimetypes.text/css": "mimetypes", + "assets.mimetypes.text/csv": "mimetypes", + "assets.mimetypes.text/html": "mimetypes", + "assets.mimetypes.text/plain": "mimetypes", + "assets.mimetypes.text/rtf": "mimetypes", + "assets.mimetypes.text/vtt": "mimetypes", + "assets.mimetypes.video": "mimetypes", + "core.accounts": "admin", + "core.add": "moodle", + "core.agelocationverification": "moodle", + "core.ago": "message", + "core.all": "moodle", + "core.allgroups": "moodle", + "core.allparticipants": "moodle", + "core.answer": "moodle", + "core.answered": "quiz", + "core.areyousure": "moodle", + "core.back": "moodle", + "core.block.blocks": "moodle", + "core.browser": "local_moodlemobileapp", + "core.cancel": "moodle", + "core.cannotconnect": "local_moodlemobileapp", + "core.cannotconnecttrouble": "local_moodlemobileapp", + "core.cannotconnectverify": "local_moodlemobileapp", + "core.cannotdownloadfiles": "local_moodlemobileapp", + "core.cannotopeninapp": "local_moodlemobileapp", + "core.cannotopeninappdownload": "local_moodlemobileapp", + "core.captureaudio": "local_moodlemobileapp", + "core.capturedimage": "local_moodlemobileapp", + "core.captureimage": "local_moodlemobileapp", + "core.capturevideo": "local_moodlemobileapp", + "core.category": "moodle", + "core.choose": "moodle", + "core.choosedots": "moodle", + "core.clearsearch": "local_moodlemobileapp", + "core.clearstoreddata": "local_moodlemobileapp", + "core.clicktohideshow": "moodle", + "core.clicktoseefull": "local_moodlemobileapp", + "core.close": "repository", + "core.comments": "moodle", + "core.comments.addcomment": "moodle", + "core.comments.comments": "moodle", + "core.comments.commentscount": "moodle", + "core.comments.commentsnotworking": "local_moodlemobileapp", + "core.comments.deletecommentbyon": "moodle", + "core.comments.eventcommentcreated": "moodle", + "core.comments.eventcommentdeleted": "moodle", + "core.comments.nocomments": "moodle", + "core.comments.savecomment": "moodle", + "core.comments.warningcommentsnotsent": "local_moodlemobileapp", + "core.commentscount": "moodle", + "core.completion-alt-auto-fail": "completion", + "core.completion-alt-auto-n": "completion", + "core.completion-alt-auto-n-override": "completion", + "core.completion-alt-auto-pass": "completion", + "core.completion-alt-auto-y": "completion", + "core.completion-alt-auto-y-override": "completion", + "core.completion-alt-manual-n": "completion", + "core.completion-alt-manual-n-override": "completion", + "core.completion-alt-manual-y": "completion", + "core.completion-alt-manual-y-override": "completion", + "core.confirmcanceledit": "local_moodlemobileapp", + "core.confirmdeletefile": "repository", + "core.confirmgotabroot": "local_moodlemobileapp", + "core.confirmgotabrootdefault": "local_moodlemobileapp", + "core.confirmleaveunknownchanges": "local_moodlemobileapp", + "core.confirmloss": "local_moodlemobileapp", + "core.confirmopeninbrowser": "local_moodlemobileapp", + "core.considereddigitalminor": "moodle", + "core.content": "moodle", + "core.contenteditingsynced": "local_moodlemobileapp", + "core.contentlinks.chooseaccount": "local_moodlemobileapp", + "core.contentlinks.chooseaccounttoopenlink": "local_moodlemobileapp", + "core.contentlinks.confirmurlothersite": "local_moodlemobileapp", + "core.contentlinks.errornoactions": "local_moodlemobileapp", + "core.contentlinks.errornosites": "local_moodlemobileapp", + "core.contentlinks.errorredirectothersite": "local_moodlemobileapp", + "core.continue": "moodle", + "core.copiedtoclipboard": "local_moodlemobileapp", + "core.copytoclipboard": "local_moodlemobileapp", + "core.course": "moodle", + "core.course.activitydisabled": "local_moodlemobileapp", + "core.course.activitynotyetviewableremoteaddon": "local_moodlemobileapp", + "core.course.activitynotyetviewablesiteupgradeneeded": "local_moodlemobileapp", + "core.course.allsections": "local_moodlemobileapp", + "core.course.askadmintosupport": "local_moodlemobileapp", + "core.course.availablespace": "local_moodlemobileapp", + "core.course.cannotdeletewhiledownloading": "local_moodlemobileapp", + "core.course.confirmdeletemodulefiles": "local_moodlemobileapp", + "core.course.confirmdeletestoreddata": "local_moodlemobileapp", + "core.course.confirmdownload": "local_moodlemobileapp", + "core.course.confirmdownloadunknownsize": "local_moodlemobileapp", + "core.course.confirmdownloadzerosize": "local_moodlemobileapp", + "core.course.confirmlimiteddownload": "local_moodlemobileapp", + "core.course.confirmpartialdownloadsize": "local_moodlemobileapp", + "core.course.contents": "local_moodlemobileapp", + "core.course.couldnotloadsectioncontent": "local_moodlemobileapp", + "core.course.couldnotloadsections": "local_moodlemobileapp", + "core.course.coursesummary": "moodle", + "core.course.downloadcourse": "tool_mobile", + "core.course.errordownloadingcourse": "local_moodlemobileapp", + "core.course.errordownloadingsection": "local_moodlemobileapp", + "core.course.errorgetmodule": "local_moodlemobileapp", + "core.course.hiddenfromstudents": "moodle", + "core.course.hiddenoncoursepage": "moodle", + "core.course.insufficientavailablequota": "local_moodlemobileapp", + "core.course.insufficientavailablespace": "local_moodlemobileapp", + "core.course.manualcompletionnotsynced": "local_moodlemobileapp", + "core.course.nocontentavailable": "local_moodlemobileapp", + "core.course.overriddennotice": "grades", + "core.course.refreshcourse": "local_moodlemobileapp", + "core.course.sections": "moodle", + "core.course.useactivityonbrowser": "local_moodlemobileapp", + "core.course.warningmanualcompletionmodified": "local_moodlemobileapp", + "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp", + "core.coursedetails": "moodle", + "core.coursenogroups": "local_moodlemobileapp", + "core.courses.addtofavourites": "block_myoverview", + "core.courses.allowguests": "enrol_guest", + "core.courses.availablecourses": "moodle", + "core.courses.cannotretrievemorecategories": "local_moodlemobileapp", + "core.courses.categories": "moodle", + "core.courses.confirmselfenrol": "local_moodlemobileapp", + "core.courses.courses": "moodle", + "core.courses.downloadcourses": "local_moodlemobileapp", + "core.courses.enrolme": "local_moodlemobileapp", + "core.courses.errorloadcategories": "local_moodlemobileapp", + "core.courses.errorloadcourses": "local_moodlemobileapp", + "core.courses.errorloadplugins": "local_moodlemobileapp", + "core.courses.errorsearching": "local_moodlemobileapp", + "core.courses.errorselfenrol": "local_moodlemobileapp", + "core.courses.filtermycourses": "local_moodlemobileapp", + "core.courses.frontpage": "admin", + "core.courses.hidecourse": "block_myoverview", + "core.courses.ignore": "admin", + "core.courses.mycourses": "moodle", + "core.courses.mymoodle": "admin", + "core.courses.nocourses": "my", + "core.courses.nocoursesyet": "moodle", + "core.courses.nosearchresults": "wiki", + "core.courses.notenroled": "completion", + "core.courses.notenrollable": "local_moodlemobileapp", + "core.courses.password": "local_moodlemobileapp", + "core.courses.paymentrequired": "moodle", + "core.courses.paypalaccepted": "enrol_paypal", + "core.courses.reload": "moodle", + "core.courses.removefromfavourites": "block_myoverview", + "core.courses.search": "moodle", + "core.courses.searchcourses": "moodle", + "core.courses.searchcoursesadvice": "local_moodlemobileapp", + "core.courses.selfenrolment": "local_moodlemobileapp", + "core.courses.sendpaymentbutton": "enrol_paypal", + "core.courses.show": "block_myoverview", + "core.courses.totalcoursesearchresults": "local_moodlemobileapp", + "core.currentdevice": "local_moodlemobileapp", + "core.datastoredoffline": "local_moodlemobileapp", + "core.date": "moodle", + "core.day": "moodle", + "core.days": "moodle", + "core.decsep": "langconfig", + "core.defaultvalue": "tool_usertours", + "core.delete": "moodle", + "core.deletedoffline": "local_moodlemobileapp", + "core.deleteduser": "bulkusers", + "core.deleting": "local_moodlemobileapp", + "core.description": "moodle", + "core.desktop": "local_moodlemobileapp", + "core.dfdaymonthyear": "local_moodlemobileapp", + "core.dfdayweekmonth": "local_moodlemobileapp", + "core.dffulldate": "local_moodlemobileapp", + "core.dflastweekdate": "local_moodlemobileapp", + "core.dfmediumdate": "local_moodlemobileapp", + "core.dftimedate": "local_moodlemobileapp", + "core.digitalminor": "moodle", + "core.digitalminor_desc": "moodle", + "core.discard": "local_moodlemobileapp", + "core.dismiss": "local_moodlemobileapp", + "core.displayoptions": "atto_media", + "core.done": "survey", + "core.download": "moodle", + "core.downloaded": "local_moodlemobileapp", + "core.downloadfile": "moodle", + "core.downloading": "local_moodlemobileapp", + "core.edit": "moodle", + "core.editor.autosavesucceeded": "editor_atto", + "core.editor.bold": "atto_bold/pluginname", + "core.editor.clear": "atto_clear/pluginname", + "core.editor.h3": "atto_title", + "core.editor.h4": "atto_title", + "core.editor.h5": "atto_title", + "core.editor.hidetoolbar": "local_moodlemobileapp", + "core.editor.italic": "atto_italic/pluginname", + "core.editor.orderedlist": "atto_orderedlist/pluginname", + "core.editor.p": "atto_title", + "core.editor.strike": "atto_strike/pluginname", + "core.editor.textrecovered": "editor_atto", + "core.editor.toggle": "local_moodlemobileapp", + "core.editor.underline": "atto_underline/pluginname", + "core.editor.unorderedlist": "atto_unorderedlist/pluginname", + "core.emptysplit": "local_moodlemobileapp", + "core.error": "moodle", + "core.errorchangecompletion": "local_moodlemobileapp", + "core.errordeletefile": "local_moodlemobileapp", + "core.errordownloading": "local_moodlemobileapp", + "core.errordownloadingsomefiles": "local_moodlemobileapp", + "core.errorfileexistssamename": "local_moodlemobileapp", + "core.errorinvalidform": "local_moodlemobileapp", + "core.errorinvalidresponse": "local_moodlemobileapp", + "core.errorloadingcontent": "local_moodlemobileapp", + "core.errorofflinedisabled": "local_moodlemobileapp", + "core.erroropenfilenoapp": "local_moodlemobileapp", + "core.erroropenfilenoextension": "local_moodlemobileapp", + "core.erroropenpopup": "local_moodlemobileapp", + "core.errorrenamefile": "local_moodlemobileapp", + "core.errorsomedatanotdownloaded": "local_moodlemobileapp", + "core.errorsync": "local_moodlemobileapp", + "core.errorsyncblocked": "local_moodlemobileapp", + "core.errorurlschemeinvalidscheme": "local_moodlemobileapp", + "core.errorurlschemeinvalidsite": "local_moodlemobileapp", + "core.explanationdigitalminor": "moodle", + "core.favourites": "moodle", + "core.filename": "repository", + "core.filenameexist": "local_moodlemobileapp", + "core.filenotfound": "resource", + "core.fileuploader.addfiletext": "repository", + "core.fileuploader.audio": "local_moodlemobileapp", + "core.fileuploader.camera": "local_moodlemobileapp", + "core.fileuploader.confirmuploadfile": "local_moodlemobileapp", + "core.fileuploader.confirmuploadunknownsize": "local_moodlemobileapp", + "core.fileuploader.errorcapturingaudio": "local_moodlemobileapp", + "core.fileuploader.errorcapturingimage": "local_moodlemobileapp", + "core.fileuploader.errorcapturingvideo": "local_moodlemobileapp", + "core.fileuploader.errorgettingimagealbum": "local_moodlemobileapp", + "core.fileuploader.errormustbeonlinetoupload": "local_moodlemobileapp", + "core.fileuploader.errornoapp": "local_moodlemobileapp", + "core.fileuploader.errorreadingfile": "local_moodlemobileapp", + "core.fileuploader.errorwhileuploading": "local_moodlemobileapp", + "core.fileuploader.file": "local_moodlemobileapp", + "core.fileuploader.filesofthesetypes": "form", + "core.fileuploader.fileuploaded": "local_moodlemobileapp", + "core.fileuploader.invalidfiletype": "repository", + "core.fileuploader.maxbytesfile": "local_moodlemobileapp", + "core.fileuploader.more": "data", + "core.fileuploader.photoalbums": "local_moodlemobileapp", + "core.fileuploader.readingfile": "local_moodlemobileapp", + "core.fileuploader.readingfileperc": "local_moodlemobileapp", + "core.fileuploader.selectafile": "local_moodlemobileapp", + "core.fileuploader.uploadafile": "local_moodlemobileapp", + "core.fileuploader.uploading": "local_moodlemobileapp", + "core.fileuploader.uploadingperc": "local_moodlemobileapp", + "core.fileuploader.video": "local_moodlemobileapp", + "core.filter": "moodle", + "core.folder": "moodle", + "core.forcepasswordchangenotice": "moodle", + "core.fulllistofcourses": "moodle", + "core.fullnameandsitename": "local_moodlemobileapp", + "core.grades.average": "grades", + "core.grades.badgrade": "grades", + "core.grades.contributiontocoursetotal": "grades", + "core.grades.feedback": "grades", + "core.grades.grade": "grades", + "core.grades.gradeitem": "grades", + "core.grades.grades": "grades", + "core.grades.lettergrade": "grades", + "core.grades.nogradesreturned": "grades", + "core.grades.nooutcome": "grades", + "core.grades.percentage": "grades", + "core.grades.range": "grades", + "core.grades.rank": "grades", + "core.grades.weight": "grades", + "core.group": "moodle", + "core.groupsseparate": "moodle", + "core.groupsvisible": "moodle", + "core.h5p.additionallicenseinfo": "h5p", + "core.h5p.author": "h5p", + "core.h5p.authorcomments": "h5p", + "core.h5p.authorcommentsdescription": "h5p", + "core.h5p.authorname": "h5p", + "core.h5p.authorrole": "h5p", + "core.h5p.by": "h5p", + "core.h5p.cancellabel": "h5p", + "core.h5p.ccattribution": "h5p", + "core.h5p.ccattributionnc": "h5p", + "core.h5p.ccattributionncnd": "h5p", + "core.h5p.ccattributionncsa": "h5p", + "core.h5p.ccattributionnd": "h5p", + "core.h5p.ccattributionsa": "h5p", + "core.h5p.ccpdd": "h5p", + "core.h5p.changedby": "h5p", + "core.h5p.changedescription": "h5p", + "core.h5p.changelog": "h5p", + "core.h5p.changeplaceholder": "h5p", + "core.h5p.close": "h5p", + "core.h5p.confirmdialogbody": "h5p", + "core.h5p.confirmdialogheader": "h5p", + "core.h5p.confirmlabel": "h5p", + "core.h5p.connectionLost": "h5p", + "core.h5p.connectionReestablished": "h5p", + "core.h5p.contentCopied": "h5p", + "core.h5p.contentchanged": "h5p", + "core.h5p.contenttype": "h5p", + "core.h5p.copyright": "h5p", + "core.h5p.copyrightinfo": "h5p", + "core.h5p.copyrightstring": "h5p", + "core.h5p.copyrighttitle": "h5p", + "core.h5p.creativecommons": "h5p", + "core.h5p.date": "h5p", + "core.h5p.disablefullscreen": "h5p", + "core.h5p.download": "h5p", + "core.h5p.downloadtitle": "h5p", + "core.h5p.editor": "h5p", + "core.h5p.embed": "h5p", + "core.h5p.embedtitle": "h5p", + "core.h5p.errorgetemail": "local_moodlemobileapp", + "core.h5p.fullscreen": "h5p", + "core.h5p.gpl": "h5p", + "core.h5p.h5ptitle": "h5p", + "core.h5p.hideadvanced": "h5p", + "core.h5p.license": "h5p", + "core.h5p.licenseCC010": "h5p", + "core.h5p.licenseCC010U": "h5p", + "core.h5p.licenseCC10": "h5p", + "core.h5p.licenseCC20": "h5p", + "core.h5p.licenseCC25": "h5p", + "core.h5p.licenseCC30": "h5p", + "core.h5p.licenseCC40": "h5p", + "core.h5p.licenseGPL": "h5p", + "core.h5p.licenseV1": "h5p", + "core.h5p.licenseV2": "h5p", + "core.h5p.licenseV3": "h5p", + "core.h5p.licensee": "h5p", + "core.h5p.licenseextras": "h5p", + "core.h5p.licenseversion": "h5p", + "core.h5p.nocopyright": "h5p", + "core.h5p.offlineDialogBody": "h5p", + "core.h5p.offlineDialogHeader": "h5p", + "core.h5p.offlineDialogRetryButtonLabel": "h5p", + "core.h5p.offlineDialogRetryMessage": "h5p", + "core.h5p.offlineSuccessfulSubmit": "h5p", + "core.h5p.offlinedisabled": "local_moodlemobileapp", + "core.h5p.originator": "h5p", + "core.h5p.pd": "h5p", + "core.h5p.pddl": "h5p", + "core.h5p.pdm": "h5p", + "core.h5p.play": "local_moodlemobileapp", + "core.h5p.resizescript": "h5p", + "core.h5p.resubmitScores": "h5p", + "core.h5p.reuse": "h5p", + "core.h5p.reuseContent": "h5p", + "core.h5p.reuseDescription": "h5p", + "core.h5p.showadvanced": "h5p", + "core.h5p.showless": "h5p", + "core.h5p.showmore": "h5p", + "core.h5p.size": "h5p", + "core.h5p.source": "h5p", + "core.h5p.startingover": "h5p", + "core.h5p.sublevel": "h5p", + "core.h5p.thumbnail": "h5p", + "core.h5p.title": "h5p", + "core.h5p.undisclosed": "h5p", + "core.h5p.year": "h5p", + "core.h5p.years": "h5p", + "core.h5p.yearsfrom": "h5p", + "core.h5p.yearsto": "h5p", + "core.hasdatatosync": "local_moodlemobileapp", + "core.help": "moodle", + "core.hide": "moodle", + "core.hour": "moodle", + "core.hours": "moodle", + "core.humanreadablesize": "local_moodlemobileapp", + "core.image": "local_moodlemobileapp", + "core.imageviewer": "local_moodlemobileapp", + "core.info": "moodle", + "core.invalidformdata": "error", + "core.labelsep": "langconfig", + "core.lastaccess": "moodle", + "core.lastdownloaded": "local_moodlemobileapp", + "core.lastmodified": "moodle", + "core.lastsync": "local_moodlemobileapp", + "core.layoutgrid": "workshopform_rubric", + "core.list": "moodle", + "core.listsep": "langconfig", + "core.loading": "moodle", + "core.loadmore": "local_moodlemobileapp", + "core.location": "moodle", + "core.login.auth_email": "auth_email/pluginname", + "core.login.authenticating": "local_moodlemobileapp", + "core.login.cancel": "moodle", + "core.login.changepassword": "moodle", + "core.login.changepasswordbutton": "local_moodlemobileapp", + "core.login.changepasswordhelp": "local_moodlemobileapp", + "core.login.changepasswordinstructions": "local_moodlemobileapp", + "core.login.changepasswordlogoutinstructions": "local_moodlemobileapp", + "core.login.changepasswordreconnectinstructions": "local_moodlemobileapp", + "core.login.confirmdeletesite": "local_moodlemobileapp", + "core.login.connect": "local_moodlemobileapp", + "core.login.connecttomoodle": "local_moodlemobileapp", + "core.login.connecttomoodleapp": "local_moodlemobileapp", + "core.login.connecttoworkplaceapp": "local_moodlemobileapp", + "core.login.contactyouradministrator": "local_moodlemobileapp", + "core.login.contactyouradministratorissue": "local_moodlemobileapp", + "core.login.createaccount": "moodle", + "core.login.createuserandpass": "moodle", + "core.login.credentialsdescription": "local_moodlemobileapp", + "core.login.emailconfirmsent": "moodle", + "core.login.emailconfirmsentnoemail": "local_moodlemobileapp", + "core.login.emailconfirmsentsuccess": "moodle", + "core.login.emailnotmatch": "local_moodlemobileapp", + "core.login.erroraccesscontrolalloworigin": "local_moodlemobileapp", + "core.login.errordeletesite": "local_moodlemobileapp", + "core.login.errorexampleurl": "local_moodlemobileapp", + "core.login.errorqrnoscheme": "local_moodlemobileapp", + "core.login.errorupdatesite": "local_moodlemobileapp", + "core.login.faqcannotconnectanswer": "local_moodlemobileapp", + "core.login.faqcannotconnectquestion": "local_moodlemobileapp", + "core.login.faqcannotfindmysiteanswer": "local_moodlemobileapp", + "core.login.faqcannotfindmysitequestion": "local_moodlemobileapp", + "core.login.faqsetupsiteanswer": "local_moodlemobileapp", + "core.login.faqsetupsitelinktitle": "local_moodlemobileapp", + "core.login.faqsetupsitequestion": "local_moodlemobileapp", + "core.login.faqtestappanswer": "local_moodlemobileapp", + "core.login.faqtestappquestion": "local_moodlemobileapp", + "core.login.faqwhatisurlanswer": "local_moodlemobileapp", + "core.login.faqwhatisurlquestion": "local_moodlemobileapp", + "core.login.faqwhereisqrcode": "local_moodlemobileapp", + "core.login.faqwhereisqrcodeanswer": "local_moodlemobileapp", + "core.login.findyoursite": "local_moodlemobileapp", + "core.login.firsttime": "moodle", + "core.login.forcepasswordchangenotice": "moodle", + "core.login.forgotten": "moodle", + "core.login.help": "moodle", + "core.login.helpmelogin": "local_moodlemobileapp", + "core.login.instructions": "auth", + "core.login.invalidaccount": "local_moodlemobileapp", + "core.login.invaliddate": "calendar/errorinvaliddate", + "core.login.invalidemail": "moodle", + "core.login.invalidmoodleversion": "local_moodlemobileapp", + "core.login.invalidsite": "local_moodlemobileapp", + "core.login.invalidtime": "local_moodlemobileapp", + "core.login.invalidurl": "scorm", + "core.login.invalidvaluemax": "local_moodlemobileapp", + "core.login.invalidvaluemin": "local_moodlemobileapp", + "core.login.localmobileunexpectedresponse": "local_moodlemobileapp", + "core.login.loggedoutssodescription": "local_moodlemobileapp", + "core.login.login": "moodle", + "core.login.loginbutton": "local_moodlemobileapp", + "core.login.logininsiterequired": "local_moodlemobileapp", + "core.login.loginsteps": "moodle", + "core.login.missingemail": "moodle", + "core.login.missingfirstname": "moodle", + "core.login.missinglastname": "moodle", + "core.login.mobileservicesnotenabled": "local_moodlemobileapp", + "core.login.mustconfirm": "moodle", + "core.login.newaccount": "moodle", + "core.login.notloggedin": "local_moodlemobileapp", + "core.login.onboardingcreatemanagecourses": "local_moodlemobileapp", + "core.login.onboardingenrolmanagestudents": "local_moodlemobileapp", + "core.login.onboardinggetstarted": "local_moodlemobileapp", + "core.login.onboardingialreadyhaveasite": "local_moodlemobileapp", + "core.login.onboardingimalearner": "local_moodlemobileapp", + "core.login.onboardingimaneducator": "local_moodlemobileapp", + "core.login.onboardingineedasite": "local_moodlemobileapp", + "core.login.onboardingprovidefeedback": "local_moodlemobileapp", + "core.login.onboardingtoconnect": "local_moodlemobileapp", + "core.login.onboardingwelcome": "local_moodlemobileapp", + "core.login.or": "local_moodlemobileapp", + "core.login.password": "moodle", + "core.login.passwordforgotten": "moodle", + "core.login.passwordforgotteninstructions2": "moodle", + "core.login.passwordrequired": "local_moodlemobileapp", + "core.login.policyaccept": "moodle", + "core.login.policyagree": "moodle", + "core.login.policyagreement": "moodle", + "core.login.policyagreementclick": "moodle", + "core.login.potentialidps": "auth", + "core.login.profileinvaliddata": "admin", + "core.login.recaptchachallengeimage": "local_moodlemobileapp", + "core.login.recaptchaexpired": "local_moodlemobileapp", + "core.login.recaptchaincorrect": "local_moodlemobileapp", + "core.login.reconnect": "local_moodlemobileapp", + "core.login.reconnectdescription": "local_moodlemobileapp", + "core.login.reconnectssodescription": "local_moodlemobileapp", + "core.login.resendemail": "moodle", + "core.login.searchby": "local_moodlemobileapp", + "core.login.security_question": "auth", + "core.login.selectacountry": "moodle", + "core.login.selectsite": "local_moodlemobileapp", + "core.login.signupplugindisabled": "local_moodlemobileapp", + "core.login.signuprequiredfieldnotsupported": "local_moodlemobileapp", + "core.login.siteaddress": "local_moodlemobileapp", + "core.login.siteaddressplaceholder": "donottranslate", + "core.login.sitehasredirect": "local_moodlemobileapp", + "core.login.siteinmaintenance": "local_moodlemobileapp", + "core.login.sitepolicynotagreederror": "local_moodlemobileapp", + "core.login.siteurl": "local_moodlemobileapp", + "core.login.siteurlrequired": "local_moodlemobileapp", + "core.login.startsignup": "moodle", + "core.login.stillcantconnect": "local_moodlemobileapp", + "core.login.supplyinfo": "moodle", + "core.login.username": "moodle", + "core.login.usernameoremail": "moodle", + "core.login.usernamerequired": "local_moodlemobileapp", + "core.login.usernotaddederror": "error", + "core.login.visitchangepassword": "local_moodlemobileapp", + "core.login.webservicesnotenabled": "local_moodlemobileapp", + "core.login.youcanstillconnectwithcredentials": "local_moodlemobileapp", + "core.login.yourenteredsite": "local_moodlemobileapp", + "core.lostconnection": "local_moodlemobileapp", + "core.mainmenu.changesite": "local_moodlemobileapp", + "core.mainmenu.help": "moodle", + "core.mainmenu.logout": "moodle", + "core.mainmenu.website": "local_moodlemobileapp", + "core.maxsizeandattachments": "moodle", + "core.min": "moodle", + "core.mins": "moodle", + "core.misc": "admin", + "core.mod_assign": "assign/pluginname", + "core.mod_assignment": "assignment/pluginname", + "core.mod_book": "book/pluginname", + "core.mod_chat": "chat/pluginname", + "core.mod_choice": "choice/pluginname", + "core.mod_data": "data/pluginname", + "core.mod_database": "data/pluginname", + "core.mod_external-tool": "lti/pluginname", + "core.mod_feedback": "feedback/pluginname", + "core.mod_file": "moodle/file", + "core.mod_folder": "folder/pluginname", + "core.mod_forum": "forum/pluginname", + "core.mod_glossary": "glossary/pluginname", + "core.mod_h5pactivity": "h5pactivity/pluginname", + "core.mod_ims": "imscp/pluginname", + "core.mod_imscp": "imscp/pluginname", + "core.mod_label": "label/pluginname", + "core.mod_lesson": "lesson/pluginname", + "core.mod_lti": "lti/pluginname", + "core.mod_page": "page/pluginname", + "core.mod_quiz": "quiz/pluginname", + "core.mod_resource": "resource/pluginname", + "core.mod_scorm": "scorm/pluginname", + "core.mod_survey": "survey/pluginname", + "core.mod_url": "url/pluginname", + "core.mod_wiki": "wiki/pluginname", + "core.mod_workshop": "workshop/pluginname", + "core.moduleintro": "moodle", + "core.more": "moodle", + "core.mygroups": "group", + "core.name": "moodle", + "core.needhelp": "local_moodlemobileapp", + "core.networkerroriframemsg": "local_moodlemobileapp", + "core.networkerrormsg": "local_moodlemobileapp", + "core.never": "moodle", + "core.next": "moodle", + "core.no": "moodle", + "core.nocomments": "moodle", + "core.nograde": "moodle", + "core.none": "moodle", + "core.nooptionavailable": "local_moodlemobileapp", + "core.nopasswordchangeforced": "local_moodlemobileapp", + "core.nopermissionerror": "local_moodlemobileapp", + "core.nopermissions": "error", + "core.noresults": "moodle", + "core.noselection": "form", + "core.notapplicable": "local_moodlemobileapp", + "core.notavailable": "moodle", + "core.notenrolledprofile": "moodle", + "core.notice": "moodle", + "core.notingroup": "moodle", + "core.notsent": "local_moodlemobileapp", + "core.now": "moodle", + "core.nummore": "local_moodlemobileapp", + "core.numwords": "moodle", + "core.offline": "message", + "core.ok": "moodle", + "core.online": "message", + "core.openfile": "local_moodlemobileapp", + "core.openfullimage": "local_moodlemobileapp", + "core.openinbrowser": "local_moodlemobileapp", + "core.openmodinbrowser": "local_moodlemobileapp", + "core.othergroups": "group", + "core.pagea": "moodle", + "core.parentlanguage": "langconfig", + "core.paymentinstant": "moodle", + "core.percentagenumber": "local_moodlemobileapp", + "core.phone": "moodle", + "core.pictureof": "moodle", + "core.previous": "moodle", + "core.proceed": "moodle", + "core.pulltorefresh": "local_moodlemobileapp", + "core.qrscanner": "local_moodlemobileapp", + "core.question.answer": "question", + "core.question.answersaved": "question", + "core.question.cannotdeterminestatus": "local_moodlemobileapp", + "core.question.certainty": "qbehaviour_deferredcbm", + "core.question.complete": "question", + "core.question.correct": "question", + "core.question.errorattachmentsnotsupported": "local_moodlemobileapp", + "core.question.errorinlinefilesnotsupported": "local_moodlemobileapp", + "core.question.errorquestionnotsupported": "local_moodlemobileapp", + "core.question.feedback": "question", + "core.question.howtodraganddrop": "local_moodlemobileapp", + "core.question.incorrect": "question", + "core.question.information": "question", + "core.question.invalidanswer": "question", + "core.question.notanswered": "question", + "core.question.notyetanswered": "question", + "core.question.partiallycorrect": "question", + "core.question.questionmessage": "local_moodlemobileapp", + "core.question.questionno": "question", + "core.question.requiresgrading": "question", + "core.quotausage": "moodle", + "core.rating.aggregateavg": "rating", + "core.rating.aggregatecount": "rating", + "core.rating.aggregatemax": "rating", + "core.rating.aggregatemin": "rating", + "core.rating.aggregatesum": "rating", + "core.rating.noratings": "rating", + "core.rating.rating": "rating", + "core.rating.ratings": "rating", + "core.redirectingtosite": "local_moodlemobileapp", + "core.refresh": "moodle", + "core.remove": "moodle", + "core.removefiles": "local_moodlemobileapp", + "core.required": "moodle", + "core.requireduserdatamissing": "local_moodlemobileapp", + "core.resourcedisplayopen": "moodle", + "core.resources": "moodle", + "core.restore": "moodle", + "core.restricted": "moodle", + "core.retry": "local_moodlemobileapp", + "core.save": "moodle", + "core.savechanges": "assign", + "core.scanqr": "local_moodlemobileapp", + "core.search": "moodle", + "core.searching": "local_moodlemobileapp", + "core.searchresults": "moodle", + "core.sec": "moodle", + "core.secs": "moodle", + "core.seemoredetail": "survey", + "core.selectacategory": "moodle", + "core.selectacourse": "moodle", + "core.selectagroup": "moodle", + "core.send": "message", + "core.sending": "chat", + "core.serverconnection": "error", + "core.settings.about": "local_moodlemobileapp", + "core.settings.appsettings": "local_moodlemobileapp", + "core.settings.appversion": "local_moodlemobileapp", + "core.settings.cannotsyncloggedout": "local_moodlemobileapp", + "core.settings.cannotsyncoffline": "local_moodlemobileapp", + "core.settings.cannotsyncwithoutwifi": "local_moodlemobileapp", + "core.settings.colorscheme": "local_moodlemobileapp", + "core.settings.colorscheme-auto": "local_moodlemobileapp", + "core.settings.colorscheme-dark": "local_moodlemobileapp", + "core.settings.colorscheme-light": "local_moodlemobileapp", + "core.settings.compilationinfo": "local_moodlemobileapp", + "core.settings.copyinfo": "local_moodlemobileapp", + "core.settings.cordovadevicemodel": "local_moodlemobileapp", + "core.settings.cordovadeviceosversion": "local_moodlemobileapp", + "core.settings.cordovadeviceplatform": "local_moodlemobileapp", + "core.settings.cordovadeviceuuid": "local_moodlemobileapp", + "core.settings.cordovaversion": "local_moodlemobileapp", + "core.settings.currentlanguage": "moodle", + "core.settings.debugdisplay": "admin", + "core.settings.debugdisplaydescription": "local_moodlemobileapp", + "core.settings.deletesitefiles": "local_moodlemobileapp", + "core.settings.deletesitefilestitle": "local_moodlemobileapp", + "core.settings.deviceinfo": "local_moodlemobileapp", + "core.settings.deviceos": "local_moodlemobileapp", + "core.settings.disableall": "message", + "core.settings.disabled": "lesson", + "core.settings.displayformat": "local_moodlemobileapp", + "core.settings.enabledownloadsection": "local_moodlemobileapp", + "core.settings.enablefirebaseanalytics": "local_moodlemobileapp", + "core.settings.enablefirebaseanalyticsdescription": "local_moodlemobileapp", + "core.settings.enablerichtexteditor": "local_moodlemobileapp", + "core.settings.enablerichtexteditordescription": "local_moodlemobileapp", + "core.settings.enablesyncwifi": "local_moodlemobileapp", + "core.settings.entriesincache": "local_moodlemobileapp", + "core.settings.errordeletesitefiles": "local_moodlemobileapp", + "core.settings.errorsyncsite": "local_moodlemobileapp", + "core.settings.estimatedfreespace": "local_moodlemobileapp", + "core.settings.filesystemroot": "local_moodlemobileapp", + "core.settings.fontsize": "local_moodlemobileapp", + "core.settings.fontsizecharacter": "block_accessibility/char", + "core.settings.forcedsetting": "local_moodlemobileapp", + "core.settings.general": "moodle", + "core.settings.language": "moodle", + "core.settings.license": "moodle", + "core.settings.localnotifavailable": "local_moodlemobileapp", + "core.settings.locationhref": "local_moodlemobileapp", + "core.settings.locked": "admin", + "core.settings.loggedin": "message", + "core.settings.loggedoff": "message", + "core.settings.navigatorlanguage": "local_moodlemobileapp", + "core.settings.navigatoruseragent": "local_moodlemobileapp", + "core.settings.networkstatus": "local_moodlemobileapp", + "core.settings.opensourcelicenses": "local_moodlemobileapp", + "core.settings.preferences": "moodle", + "core.settings.privacypolicy": "local_moodlemobileapp", + "core.settings.publisher": "local_moodlemobileapp", + "core.settings.pushid": "local_moodlemobileapp", + "core.settings.reportinbackground": "local_moodlemobileapp", + "core.settings.screen": "local_moodlemobileapp", + "core.settings.settings": "moodle", + "core.settings.showdownloadoptions": "local_moodlemobileapp", + "core.settings.siteinfo": "local_moodlemobileapp", + "core.settings.sites": "moodle", + "core.settings.spaceusage": "local_moodlemobileapp", + "core.settings.spaceusagehelp": "local_moodlemobileapp", + "core.settings.synchronization": "local_moodlemobileapp", + "core.settings.synchronizenow": "local_moodlemobileapp", + "core.settings.synchronizenowhelp": "local_moodlemobileapp", + "core.settings.syncsettings": "local_moodlemobileapp", + "core.settings.total": "moodle", + "core.settings.wificonnection": "local_moodlemobileapp", + "core.sharedfiles.chooseaccountstorefile": "local_moodlemobileapp", + "core.sharedfiles.chooseactionrepeatedfile": "local_moodlemobileapp", + "core.sharedfiles.errorreceivefilenosites": "local_moodlemobileapp", + "core.sharedfiles.nosharedfiles": "local_moodlemobileapp", + "core.sharedfiles.nosharedfilestoupload": "local_moodlemobileapp", + "core.sharedfiles.rename": "local_moodlemobileapp", + "core.sharedfiles.replace": "local_moodlemobileapp", + "core.sharedfiles.sharedfiles": "local_moodlemobileapp", + "core.sharedfiles.successstorefile": "local_moodlemobileapp", + "core.show": "moodle", + "core.showless": "form", + "core.showmore": "form", + "core.site": "moodle", + "core.sitehome.sitehome": "moodle", + "core.sitehome.sitenews": "moodle", + "core.sitemaintenance": "admin", + "core.sizeb": "moodle", + "core.sizegb": "moodle", + "core.sizekb": "moodle", + "core.sizemb": "moodle", + "core.sizetb": "local_moodlemobileapp", + "core.skip": "tool_usertours", + "core.sorry": "local_moodlemobileapp", + "core.sort": "moodle", + "core.sortby": "moodle", + "core.start": "grouptool", + "core.storingfiles": "local_moodlemobileapp", + "core.strftimedate": "langconfig", + "core.strftimedatefullshort": "langconfig", + "core.strftimedateshort": "langconfig", + "core.strftimedatetime": "langconfig", + "core.strftimedatetimeshort": "langconfig", + "core.strftimedaydate": "langconfig", + "core.strftimedaydatetime": "langconfig", + "core.strftimedayshort": "langconfig", + "core.strftimedaytime": "langconfig", + "core.strftimemonthyear": "langconfig", + "core.strftimerecent": "langconfig", + "core.strftimerecentfull": "langconfig", + "core.strftimetime": "langconfig", + "core.strftimetime12": "langconfig", + "core.strftimetime24": "langconfig", + "core.submit": "moodle", + "core.success": "moodle", + "core.tablet": "local_moodlemobileapp", + "core.tag.defautltagcoll": "tag", + "core.tag.errorareanotsupported": "local_moodlemobileapp", + "core.tag.inalltagcoll": "tag", + "core.tag.itemstaggedwith": "tag", + "core.tag.noresultsfor": "tag", + "core.tag.notagsfound": "tag", + "core.tag.searchtags": "tag", + "core.tag.showingfirsttags": "tag", + "core.tag.tag": "moodle", + "core.tag.tagarea_course": "tag", + "core.tag.tagarea_course_modules": "tag", + "core.tag.tagarea_post": "tag", + "core.tag.tagarea_user": "tag", + "core.tag.tags": "moodle", + "core.tag.warningareasnotsupported": "local_moodlemobileapp", + "core.teachers": "moodle", + "core.thereisdatatosync": "local_moodlemobileapp", + "core.thisdirection": "langconfig", + "core.time": "moodle", + "core.timesup": "quiz", + "core.today": "moodle", + "core.tryagain": "local_moodlemobileapp", + "core.twoparagraphs": "local_moodlemobileapp", + "core.uhoh": "local_moodlemobileapp", + "core.unexpectederror": "local_moodlemobileapp", + "core.unicodenotsupported": "local_moodlemobileapp", + "core.unicodenotsupportedcleanerror": "local_moodlemobileapp", + "core.unknown": "local_moodlemobileapp", + "core.unlimited": "moodle", + "core.unzipping": "local_moodlemobileapp", + "core.updaterequired": "local_moodlemobileapp", + "core.updaterequireddesc": "local_moodlemobileapp", + "core.upgraderunning": "error", + "core.user": "moodle", + "core.user.address": "moodle", + "core.user.city": "moodle", + "core.user.contact": "local_moodlemobileapp", + "core.user.country": "moodle", + "core.user.description": "moodle", + "core.user.details": "report_security", + "core.user.detailsnotavailable": "local_moodlemobileapp", + "core.user.editingteacher": "moodle/defaultcourseteacher", + "core.user.email": "moodle", + "core.user.emailagain": "moodle", + "core.user.errorloaduser": "local_moodlemobileapp", + "core.user.firstname": "moodle", + "core.user.interests": "moodle", + "core.user.lastname": "moodle", + "core.user.manager": "role", + "core.user.newpicture": "moodle", + "core.user.noparticipants": "error", + "core.user.participants": "moodle", + "core.user.phone1": "moodle", + "core.user.phone2": "moodle", + "core.user.roles": "moodle", + "core.user.sendemail": "local_moodlemobileapp", + "core.user.student": "moodle/defaultcoursestudent", + "core.user.teacher": "moodle/noneditingteacher", + "core.user.webpage": "moodle", + "core.userdeleted": "moodle", + "core.userdetails": "moodle", + "core.usernotfullysetup": "error", + "core.users": "moodle", + "core.view": "moodle", + "core.viewcode": "local_moodlemobileapp", + "core.vieweditor": "local_moodlemobileapp", + "core.viewembeddedcontent": "local_moodlemobileapp", + "core.viewprofile": "moodle", + "core.warningofflinedatadeleted": "local_moodlemobileapp", + "core.whatisyourage": "moodle", + "core.wheredoyoulive": "moodle", + "core.whoissiteadmin": "local_moodlemobileapp", + "core.whoops": "local_moodlemobileapp", + "core.whyisthishappening": "local_moodlemobileapp", + "core.whyisthisrequired": "moodle", + "core.wsfunctionnotavailable": "local_moodlemobileapp", + "core.year": "moodle", + "core.years": "moodle", + "core.yes": "moodle", + "core.youreoffline": "local_moodlemobileapp", + "core.youreonline": "local_moodlemobileapp" +} diff --git a/scripts/moodle_to_json.php b/scripts/moodle_to_json.php new file mode 100644 index 000000000..f8fafba28 --- /dev/null +++ b/scripts/moodle_to_json.php @@ -0,0 +1,62 @@ +. + +/** + * Script for converting moodle strings to json. + */ + +// Check we are in CLI. +if (isset($_SERVER['REMOTE_ADDR'])) { + exit(1); +} +define('MOODLE_INTERNAL', 1); +define('LANGPACKSFOLDER', '../../moodle-langpacks'); +define('ASSETSPATH', '../src/assets/lang/'); +define('CONFIG', '../config/config.json'); +define('OVERRIDE_LANG_SUFIX', false); + +global $strings; +require_once('lang_functions.php'); + +$config = file_get_contents(CONFIG); +$config = (array) json_decode($config); +$config_langs = array_keys(get_object_vars($config['languages'])); + +// Set languages to do. If script is called using a language it will be used as unique. +if (isset($argv[1]) && !empty($argv[1])) { + $forcedetect = false; + define('TOTRANSLATE', true); + $languages = explode(',', $argv[1]); +} else { + $forcedetect = true; + define('TOTRANSLATE', false); + $languages = $config_langs; +} + +$keys = get_langindex_keys(); + +$added_langs = build_languages($languages, $keys); + +if ($forcedetect) { + $new_langs = detect_languages($languages, $keys); + + if (!empty($new_langs)) { + echo "\n\n\nThe following languages are going to be added\n\n\n"; + $added_langs = build_languages($new_langs, $keys, $added_langs); + } +} + +add_langs_to_config($added_langs, $config); diff --git a/scripts/update_lang.sh b/scripts/update_lang.sh new file mode 100755 index 000000000..dac901789 --- /dev/null +++ b/scripts/update_lang.sh @@ -0,0 +1,23 @@ +#!/bin/bash +source "functions.sh" +forceLang=$1 + +print_title 'Getting languages' +git clone --depth 1 --no-single-branch https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER +pushd $LANGPACKSFOLDER +BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_)) +BRANCH=${BRANCHES[${#BRANCHES[@]}-1]} +git checkout $BRANCH +git pull +popd + +print_title 'Getting local mobile langs' +git clone --depth 1 https://github.com/moodlehq/moodle-local_moodlemobileapp.git ../../moodle-local_moodlemobileapp + +if [ -z $forceLang ]; then + php -f moodle_to_json.php +else + php -f moodle_to_json.php "$forceLang" +fi + +print_ok 'All done!' \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8ddb2387a..e494c5f85 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -18,6 +18,8 @@ import { NavController } from '@ionic/angular'; import { CoreLangProvider } from '@services/lang'; import { CoreLoginHelperProvider } from '@core/login/services/helper'; import { CoreEvents, CoreEventSessionExpiredData } from '@singletons/events'; +import { Network, NgZone, Platform } from '@singletons/core.singletons'; +import { CoreApp } from '@services/app'; @Component({ selector: 'app-root', @@ -45,14 +47,73 @@ export class AppComponent implements OnInit { this.langProvider.clearCustomStrings(); // Remove version classes from body. - // @todo - // this.removeVersionClass(); + this.removeVersionClass(); }); // Listen for session expired events. CoreEvents.on(CoreEvents.SESSION_EXPIRED, (data: CoreEventSessionExpiredData) => { this.loginHelper.sessionExpired(data); }); + + this.onPlatformReady(); + } + + protected async onPlatformReady(): Promise { + await Platform.instance.ready(); + + // Refresh online status when changes. + Network.instance.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.instance.run(() => { + const isOnline = CoreApp.instance.isOnline(); + const hadOfflineMessage = document.body.classList.contains('core-offline'); + + document.body.classList.toggle('core-offline', !isOnline); + + if (isOnline && hadOfflineMessage) { + document.body.classList.add('core-online'); + + setTimeout(() => { + document.body.classList.remove('core-online'); + }, 3000); + } else if (!isOnline) { + document.body.classList.remove('core-online'); + } + }); + }); + } + + /** + * Convenience function to add version to body classes. + * + * @param release Current release number of the site. + */ + protected addVersionClass(release: string): void { + const parts = release.split('.', 3); + + parts[1] = parts[1] || '0'; + parts[2] = parts[2] || '0'; + + document.body.classList.add('version-' + parts[0], + 'version-' + parts[0] + '-' + parts[1], + 'version-' + parts[0] + '-' + parts[1] + '-' + parts[2]); + } + + /** + * Convenience function to remove all version classes form body. + */ + protected removeVersionClass(): void { + const remove: string[] = []; + + Array.from(document.body.classList).forEach((tempClass) => { + if (tempClass.substring(0, 8) == 'version-') { + remove.push(tempClass); + } + }); + + remove.forEach((tempClass) => { + document.body.classList.remove(tempClass); + }); } } diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 185e30155..1e314e6d9 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -28,6 +28,8 @@ import { CoreRecaptchaComponent } from './recaptcha/recaptcha'; import { CoreRecaptchaModalComponent } from './recaptcha/recaptchamodal'; import { CoreShowPasswordComponent } from './show-password/show-password'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; +import { CoreTabsComponent } from './tabs/tabs'; + import { CoreDirectivesModule } from '@app/directives/directives.module'; import { CorePipesModule } from '@app/pipes/pipes.module'; @@ -44,6 +46,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module'; CoreRecaptchaModalComponent, CoreShowPasswordComponent, CoreEmptyBoxComponent, + CoreTabsComponent, ], imports: [ CommonModule, @@ -64,6 +67,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module'; CoreRecaptchaModalComponent, CoreShowPasswordComponent, CoreEmptyBoxComponent, + CoreTabsComponent, ], }) export class CoreComponentsModule {} diff --git a/src/app/components/tabs/core-tabs.html b/src/app/components/tabs/core-tabs.html new file mode 100644 index 000000000..731d8f3e6 --- /dev/null +++ b/src/app/components/tabs/core-tabs.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + {{ tab.title | translate}} + {{ tab.badge }} + + + + + + + + + + + diff --git a/src/app/components/tabs/tabs.scss b/src/app/components/tabs/tabs.scss new file mode 100644 index 000000000..3c926bc70 --- /dev/null +++ b/src/app/components/tabs/tabs.scss @@ -0,0 +1,72 @@ +:host { + --tabs-background: var(--background); + --tabs-color: var(--color); + height: 100%; + display: block; + + ion-tabs { + background: transparent; + position: relative; + } + + ion-tab-bar.core-tabs-bar { + position: relative; + width: 100%; + background: var(--tabs-background); + color: var(--tabs-color); + -webkit-filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow))); + filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow))); + border: 0; + + ion-row { + width: 100%; + } + + .tab-slide { + border-bottom: 2px solid transparent; + min-width: 100px; + min-height: 56px; + cursor: pointer; + overflow: hidden; + + ion-tab-button { + ion-label { + font-size: 16px; + font-weight: 400; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + word-wrap: break-word; + max-width: 100%; + line-height: 1.2em; + margin-top: 16px; + margin-bottom: 16px; + } + } + + &[aria-selected=true] { + color: var(--color-active); + border-bottom-color: var(--border-color-active); + ion-tab-button { + color: var(--color-active); + } + } + } + + ion-col { + text-align: center; + line-height: 1.6rem; + + &.col-with-arrow { + display: flex; + justify-content: center; + align-items: center; + } + } + + &.tabs-hidden { + display: none !important; + transform: translateY(0) !important; + } + } +} diff --git a/src/app/components/tabs/tabs.ts b/src/app/components/tabs/tabs.ts new file mode 100644 index 000000000..10b3add1c --- /dev/null +++ b/src/app/components/tabs/tabs.ts @@ -0,0 +1,629 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + Component, + Input, + Output, + EventEmitter, + OnInit, + OnChanges, + OnDestroy, + AfterViewInit, + ViewChild, + ElementRef, +} from '@angular/core'; +import { Platform, IonSlides, IonTabs, NavController } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { Subscription } from 'rxjs'; +import { CoreApp } from '@services/app'; +import { CoreConfig } from '@services/config'; +import { CoreConstants } from '@core/constants'; +import { CoreUtils } from '@/app/services/utils/utils'; +import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; +import { Params } from '@angular/router'; + +/** + * This component displays some top scrollable tabs that will autohide on vertical scroll. + * + * Example usage: + * + * + * + * Tab contents will only be shown if that tab is selected. + * + * @todo: Test behaviour when tabs are added late. + * @todo: Test RTL and tab history. + */ +@Component({ + selector: 'core-tabs', + templateUrl: 'core-tabs.html', + styleUrls: ['tabs.scss'], +}) +export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { + + + // Minimum tab's width to display fully the word "Competencies" which is the longest tab in the app. + protected static readonly MIN_TAB_WIDTH = 107; + // Max height that allows tab hiding. + protected static readonly MAX_HEIGHT_TO_HIDE_TABS = 768; + + @Input() protected selectedIndex = 0; // Index of the tab to select. + @Input() hideUntil = false; // Determine when should the contents be shown. + /** + * Determine tabs layout. + */ + @Input() layout: 'icon-top' | 'icon-start' | 'icon-end' | 'icon-bottom' | 'icon-hide' | 'label-hide' = 'icon-hide'; + @Input() tabs: CoreTab[] = []; + @Output() protected ionChange: EventEmitter = new EventEmitter(); // Emitted when the tab changes. + + @ViewChild(IonSlides) protected slides?: IonSlides; + @ViewChild(IonTabs) protected ionTabs?: IonTabs; + + selected?: string; // Selected tab id. + showPrevButton = false; + showNextButton = false; + maxSlides = 3; + numTabsShown = 0; + direction = 'ltr'; + description = ''; + lastScroll = 0; + slideOpts = { + initialSlide: 0, + slidesPerView: 3, + centerInsufficientSlides: true, + }; + + protected initialized = false; + protected afterViewInitTriggered = false; + + protected tabBarHeight = 0; + protected tabBarElement?: HTMLIonTabBarElement; // The top tab bar element. + protected tabsElement?: HTMLIonTabsElement; // The ionTabs native Element. + protected tabsShown = true; + protected resizeFunction?: EventListenerOrEventListenerObject; + protected isDestroyed = false; + protected isCurrentView = true; + protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view. + protected hasSliddenToInitial = false; // Whether we've already slidden to the initial slide or there was no need. + protected selectHistory: string[] = []; + + protected firstSelectedTab?: string; // ID of the first selected tab to control history. + protected unregisterBackButtonAction: any; + protected languageChangedSubscription: Subscription; + protected isInTransition = false; // Weather Slides is in transition. + protected slidesSwiper: any; + protected slidesSwiperLoaded = false; + + constructor( + protected element: ElementRef, + platform: Platform, + translate: TranslateService, + protected navCtrl: NavController, + ) { + this.direction = platform.isRTL ? 'rtl' : 'ltr'; + + // Change the side when the language changes. + this.languageChangedSubscription = translate.onLangChange.subscribe(() => { + setTimeout(() => { + this.direction = platform.isRTL ? 'rtl' : 'ltr'; + }); + }); + } + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + this.tabs.forEach((tab) => { + this.initTab(tab); + }); + } + + /** + * Init tab info. + * + * @param tab Tab class. + */ + protected initTab(tab: CoreTab): void { + tab.id = tab.id || 'core-tab-' + CoreUtils.instance.getUniqueId('CoreTabsComponent'); + if (typeof tab.enabled == 'undefined') { + tab.enabled = true; + } + } + + /** + * View has been initialized. + */ + async ngAfterViewInit(): Promise { + if (this.isDestroyed) { + return; + } + + this.tabBarElement = this.element.nativeElement.querySelector('ion-tab-bar'); + this.tabsElement = this.element.nativeElement.querySelector('ion-tabs'); + + this.slidesSwiper = await this.slides?.getSwiper(); + this.slidesSwiper.once('progress', () => { + this.slidesSwiperLoaded = true; + this.calculateSlides(); + }); + + this.afterViewInitTriggered = true; + + if (!this.initialized && this.hideUntil) { + // Tabs should be shown, initialize them. + await this.initializeTabs(); + } + + this.resizeFunction = this.windowResized.bind(this); + + window.addEventListener('resize', this.resizeFunction!); + } + + /** + * Detect changes on input properties. + */ + ngOnChanges(): void { + this.tabs.forEach((tab) => { + this.initTab(tab); + }); + + // We need to wait for ngAfterViewInit because we need core-tab components to be executed. + if (!this.initialized && this.hideUntil && this.afterViewInitTriggered) { + // Tabs should be shown, initialize them. + // Use a setTimeout so child core-tab update their inputs before initializing the tabs. + setTimeout(() => { + this.initializeTabs(); + }); + } + } + + /** + * User entered the page that contains the component. + */ + ionViewDidEnter(): void { + this.isCurrentView = true; + + this.calculateSlides(); + + this.registerBackButtonAction(); + } + + /** + * Register back button action. + */ + protected registerBackButtonAction(): void { + this.unregisterBackButtonAction = CoreApp.instance.registerBackButtonAction(() => { + // The previous page in history is not the last one, we need the previous one. + if (this.selectHistory.length > 1) { + const tabIndex = this.selectHistory[this.selectHistory.length - 2]; + + // Remove curent and previous tabs from history. + this.selectHistory = this.selectHistory.filter((tabId) => this.selected != tabId && tabIndex != tabId); + + this.selectTab(tabIndex); + + return true; + } else if (this.selected != this.firstSelectedTab) { + // All history is gone but we are not in the first selected tab. + this.selectHistory = []; + + this.selectTab(this.firstSelectedTab!); + + return true; + } + + return false; + }, 750); + } + + /** + * User left the page that contains the component. + */ + ionViewDidLeave(): void { + // Unregister the custom back button action for this page + this.unregisterBackButtonAction && this.unregisterBackButtonAction(); + + this.isCurrentView = false; + } + + /** + * Calculate slides. + */ + protected async calculateSlides(): Promise { + if (!this.isCurrentView || !this.initialized) { + // Don't calculate if component isn't in current view, the calculations are wrong. + return; + } + + if (!this.tabsShown) { + if (window.innerHeight >= CoreTabsComponent.MAX_HEIGHT_TO_HIDE_TABS) { + // Ensure tabbar is shown. + this.tabsShown = true; + this.tabBarElement!.classList.remove('tabs-hidden'); + this.lastScroll = 0; + } + } + + await this.calculateMaxSlides(); + + this.updateSlides(); + } + + /** + * Calculate the tab bar height. + */ + protected calculateTabBarHeight(): void { + if (!this.tabBarElement || !this.tabsElement) { + return; + } + + this.tabBarHeight = this.tabBarElement.offsetHeight; + + if (this.tabsShown) { + // Smooth translation. + this.tabsElement.style.top = - this.lastScroll + 'px'; + this.tabsElement.style.height = 'calc(100% + ' + scroll + 'px'; + } else { + this.tabBarElement.classList.add('tabs-hidden'); + this.tabsElement.style.top = '0'; + this.tabsElement.style.height = ''; + } + } + + /** + * Get the tab on a index. + * + * @param tabId Tab ID. + * @return Selected tab. + */ + protected getTabIndex(tabId: string): number { + return this.tabs.findIndex((tab) => tabId == tab.id); + } + + /** + * Get the current selected tab. + * + * @return Selected tab. + */ + getSelected(): CoreTab | undefined { + const index = this.selected && this.getTabIndex(this.selected); + + return index && index >= 0 ? this.tabs[index] : undefined; + } + + /** + * Initialize the tabs, determining the first tab to be shown. + */ + protected async initializeTabs(): Promise { + let selectedTab: CoreTab | undefined = this.tabs[this.selectedIndex || 0] || undefined; + + if (!selectedTab || !selectedTab.enabled) { + // The tab is not enabled or not shown. Get the first tab that is enabled. + selectedTab = this.tabs.find((tab) => tab.enabled) || undefined; + } + + if (!selectedTab) { + return; + } + + this.firstSelectedTab = selectedTab.id; + this.selectTab(this.firstSelectedTab); + + // Setup tab scrolling. + this.calculateTabBarHeight(); + + this.initialized = true; + + // Check which arrows should be shown. + this.calculateSlides(); + } + + /** + * Method executed when the slides are changed. + */ + async slideChanged(): Promise { + if (!this.slidesSwiperLoaded) { + return; + } + + this.isInTransition = false; + const slidesCount = await this.slides?.length() || 0; + if (slidesCount > 0) { + this.showPrevButton = !await this.slides?.isBeginning(); + this.showNextButton = !await this.slides?.isEnd(); + } else { + this.showPrevButton = false; + this.showNextButton = false; + } + + const currentIndex = await this.slides!.getActiveIndex(); + if (this.shouldSlideToInitial && currentIndex != this.selectedIndex) { + // Current tab has changed, don't slide to initial anymore. + this.shouldSlideToInitial = false; + } + } + + /** + * Updates the number of slides to show. + */ + protected async updateSlides(): Promise { + this.numTabsShown = this.tabs.reduce((prev: number, current: CoreTab) => current.enabled ? prev + 1 : prev, 0); + + this.slideOpts.slidesPerView = Math.min(this.maxSlides, this.numTabsShown); + this.slidesSwiper.params.slidesPerView = this.slideOpts.slidesPerView; + + this.calculateTabBarHeight(); + await this.slides!.update(); + + if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.slideOpts.slidesPerView) { + this.hasSliddenToInitial = true; + this.shouldSlideToInitial = true; + + setTimeout(() => { + if (this.shouldSlideToInitial) { + this.slides!.slideTo(this.selectedIndex, 0); + this.shouldSlideToInitial = false; + } + }, 400); + + return; + } else if (this.selectedIndex) { + this.hasSliddenToInitial = true; + } + + setTimeout(() => { + this.slideChanged(); // Call slide changed again, sometimes the slide active index takes a while to be updated. + }, 400); + } + + /** + * Calculate the number of slides that can fit on the screen. + */ + protected async calculateMaxSlides(): Promise { + if (!this.slidesSwiperLoaded) { + return; + } + + this.maxSlides = 3; + const width = this.slidesSwiper.width; + if (width) { + const fontSize = await + CoreConfig.instance.get(CoreConstants.SETTINGS_FONT_SIZE, CoreConstants.CONFIG.font_sizes[0]); + + this.maxSlides = Math.floor(width / (fontSize / CoreConstants.CONFIG.font_sizes[0] * + CoreTabsComponent.MIN_TAB_WIDTH)); + } + } + + /** + * Method that shows the next tab. + */ + async slideNext(): Promise { + // Stop if slides are in transition. + if (!this.showNextButton || this.isInTransition) { + return; + } + + if (await this.slides!.isBeginning()) { + // Slide to the second page. + this.slides!.slideTo(this.maxSlides); + } else { + const currentIndex = await this.slides!.getActiveIndex(); + if (typeof currentIndex !== 'undefined') { + const nextSlideIndex = currentIndex + this.maxSlides; + this.isInTransition = true; + if (nextSlideIndex < this.numTabsShown) { + // Slide to the next page. + await this.slides!.slideTo(nextSlideIndex); + } else { + // Slide to the latest slide. + await this.slides!.slideTo(this.numTabsShown - 1); + } + } + + } + } + + /** + * Method that shows the previous tab. + */ + async slidePrev(): Promise { + // Stop if slides are in transition. + if (!this.showPrevButton || this.isInTransition) { + return; + } + + if (await this.slides!.isEnd()) { + this.slides!.slideTo(this.numTabsShown - this.maxSlides * 2); + // Slide to the previous of the latest page. + } else { + const currentIndex = await this.slides!.getActiveIndex(); + if (typeof currentIndex !== 'undefined') { + const prevSlideIndex = currentIndex - this.maxSlides; + this.isInTransition = true; + if (prevSlideIndex >= 0) { + // Slide to the previous page. + await this.slides!.slideTo(prevSlideIndex); + } else { + // Slide to the first page. + await this.slides!.slideTo(0); + } + } + } + } + + /** + * Show or hide the tabs. This is used when the user is scrolling inside a tab. + * + * @param scrollEvent Scroll event to check scroll position. + * @param content Content element to check measures. + */ + protected showHideTabs(scrollEvent: CustomEvent, content: HTMLElement): void { + if (!this.tabBarElement || !this.tabsElement || !content) { + return; + } + + // Always show on very tall screens. + if (window.innerHeight >= CoreTabsComponent.MAX_HEIGHT_TO_HIDE_TABS) { + return; + } + + if (!this.tabBarHeight && this.tabBarElement.offsetHeight != this.tabBarHeight) { + // Wrong tab height, recalculate it. + this.calculateTabBarHeight(); + } + + if (!this.tabBarHeight) { + // We don't have the tab bar height, this means the tab bar isn't shown. + return; + } + + const scroll = parseInt(scrollEvent.detail.scrollTop, 10); + if (scroll <= 0) { + // Ensure tabbar is shown. + this.tabsElement.style.top = '0'; + this.tabsElement.style.height = ''; + this.tabBarElement!.classList.remove('tabs-hidden'); + this.tabsShown = true; + this.lastScroll = 0; + + return; + } + + if (scroll == this.lastScroll) { + // Ensure scroll has been modified to avoid flicks. + return; + } + + if (this.tabsShown && scroll > this.tabBarHeight) { + this.tabsShown = false; + + // Hide tabs. + this.tabBarElement.classList.add('tabs-hidden'); + this.tabsElement.style.top = '0'; + this.tabsElement.style.height = ''; + } else if (!this.tabsShown && scroll <= this.tabBarHeight) { + this.tabsShown = true; + this.tabBarElement!.classList.remove('tabs-hidden'); + } + + if (this.tabsShown && content.scrollHeight > content.clientHeight + (this.tabBarHeight - scroll)) { + // Smooth translation. + this.tabsElement.style.top = - scroll + 'px'; + this.tabsElement.style.height = 'calc(100% + ' + scroll + 'px'; + } + // Use lastScroll after moving the tabs to avoid flickering. + this.lastScroll = parseInt(scrollEvent.detail.scrollTop, 10); + } + + /** + * Tab selected. + * + * @param tabId Selected tab index. + * @param e Event. + */ + async selectTab(tabId: string, e?: Event): Promise { + let index = this.tabs.findIndex((tab) => tabId == tab.id); + if (index < 0 || index >= this.tabs.length) { + if (this.selected) { + // Invalid index do not change tab. + e && e.preventDefault(); + e && e.stopPropagation(); + + return; + } + + // Index isn't valid, select the first one. + index = 0; + } + + const selectedTab = this.tabs[index]; + if (tabId == this.selected || !selectedTab || !selectedTab.enabled) { + // Already selected or not enabled. + + e && e.preventDefault(); + e && e.stopPropagation(); + + return; + } + + if (this.selected) { + await this.slides!.slideTo(index); + } + + const pageParams: NavigationOptions = {}; + if (selectedTab.pageParams) { + pageParams.queryParams = selectedTab.pageParams; + } + const ok = await this.navCtrl.navigateForward(selectedTab.page, pageParams); + + if (ok) { + this.selectHistory.push(tabId); + this.selected = tabId; + this.selectedIndex = index; + + this.ionChange.emit(selectedTab); + + const content = this.ionTabs!.outlet.nativeEl.querySelector('ion-content'); + + if (content) { + const scroll = await content.getScrollElement(); + content.scrollEvents = true; + content.addEventListener('ionScroll', (e: CustomEvent): void => { + this.showHideTabs(e, scroll); + }); + } + } + } + + /** + * Adapt tabs to a window resize. + */ + protected windowResized(): void { + setTimeout(() => { + this.calculateSlides(); + }); + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.isDestroyed = true; + + if (this.resizeFunction) { + window.removeEventListener('resize', this.resizeFunction); + } + } + +} + +/** + * Core Tab class. + */ +class CoreTab { + + id = ''; // Unique tab id. + class = ''; // Class, if needed. + title = ''; // The translatable tab title. + icon?: string; // The tab icon. + badge?: string; // A badge to add in the tab. + badgeStyle?: string; // The badge color. + enabled = true; // Whether the tab is enabled. + page = ''; // Page to navigate to. + pageParams?: Params; // Page params. + +} diff --git a/src/app/core/courses/courses.module.ts b/src/app/core/courses/courses.module.ts index 4dd76b82a..3665427fd 100644 --- a/src/app/core/courses/courses.module.ts +++ b/src/app/core/courses/courses.module.ts @@ -13,9 +13,27 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CoreHomeDelegate } from '../mainmenu/services/home.delegate'; +import { CoreCoursesDashboardHandler } from './handlers/dashboard'; +import { CoreCoursesDashboardPage } from './pages/dashboard/dashboard.page'; + + +const routes: Routes = [ + { + path: 'dashboard', + component: CoreCoursesDashboardPage, + }, +]; @NgModule({ - imports: [], + imports: [RouterModule.forChild(routes)], declarations: [], }) -export class CoreCoursesModule { } +export class CoreCoursesModule { + + constructor(homeDelegate: CoreHomeDelegate) { + homeDelegate.registerHandler(new CoreCoursesDashboardHandler()); + } + +} diff --git a/src/app/core/courses/handlers/dashboard.ts b/src/app/core/courses/handlers/dashboard.ts new file mode 100644 index 000000000..0640f7c66 --- /dev/null +++ b/src/app/core/courses/handlers/dashboard.ts @@ -0,0 +1,60 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreHomeHandler, CoreHomeHandlerData } from '@core/mainmenu/services/home.delegate'; + +/** + * Handler to add Home into main menu. + */ +export class CoreCoursesDashboardHandler implements CoreHomeHandler { + + name = 'CoreCoursesDashboard'; + priority = 1100; + + /** + * Check if the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + isEnabled(): Promise { + return this.isEnabledForSite(); + } + + /** + * Check if the handler is enabled on a certain site. + * + * @param siteId Site ID. If not defined, current site. + * @return Whether or not the handler is enabled on a site level. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async isEnabledForSite(siteId?: string): Promise { + // @todo + return true; + } + + /** + * Returns the data needed to render the handler. + * + * @return Data needed to render the handler. + */ + getDisplayData(): CoreHomeHandlerData { + return { + title: 'core.courses.mymoodle', + page: 'home/dashboard', + class: 'core-courses-dashboard-handler', + icon: 'fa-tachometer-alt', + }; + } + +} diff --git a/src/app/core/courses/pages/dashboard/dashboard.html b/src/app/core/courses/pages/dashboard/dashboard.html new file mode 100644 index 000000000..a2a6c650c --- /dev/null +++ b/src/app/core/courses/pages/dashboard/dashboard.html @@ -0,0 +1,6 @@ + + + +
Dashboard
+
+
diff --git a/src/app/core/courses/pages/dashboard/dashboard.page.module.ts b/src/app/core/courses/pages/dashboard/dashboard.page.module.ts new file mode 100644 index 000000000..5d71c930a --- /dev/null +++ b/src/app/core/courses/pages/dashboard/dashboard.page.module.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +import { CoreCoursesDashboardPage } from './dashboard.page'; + +const routes: Routes = [ + { + path: '', + component: CoreCoursesDashboardPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + ], + declarations: [ + CoreCoursesDashboardPage, + ], + exports: [RouterModule], +}) +export class CoreCoursesDashboardPageModule {} diff --git a/src/app/core/courses/pages/dashboard/dashboard.page.ts b/src/app/core/courses/pages/dashboard/dashboard.page.ts new file mode 100644 index 000000000..458512990 --- /dev/null +++ b/src/app/core/courses/pages/dashboard/dashboard.page.ts @@ -0,0 +1,36 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; + +/** + * Page that displays the Home. + */ +@Component({ + selector: 'page-core-courses-dashboard', + templateUrl: 'dashboard.html', + styleUrls: ['dashboard.scss'], +}) +export class CoreCoursesDashboardPage implements OnInit { + + siteName = 'Hello world'; + + /** + * Initialize the component. + */ + ngOnInit(): void { + // @todo + } + +} diff --git a/src/app/core/courses/pages/dashboard/dashboard.scss b/src/app/core/courses/pages/dashboard/dashboard.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/core/login/components/site-onboarding/site-onboarding.html b/src/app/core/login/components/site-onboarding/site-onboarding.html index 9c34463e2..0db7e52f6 100644 --- a/src/app/core/login/components/site-onboarding/site-onboarding.html +++ b/src/app/core/login/components/site-onboarding/site-onboarding.html @@ -45,7 +45,7 @@