3
0

Merge pull request from crazyserver/MOBILE-4343

[4.3] Mobile 4343
This commit is contained in:
Dani Palou 2023-06-15 17:22:49 +02:00 committed by GitHub
commit d7a18dd36c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 869 additions and 1226 deletions
gulp
gulpfile.jsmoodle.config.jsonpackage.json
scripts
src
addons
block
activityresults
badges
blogmenu
blogrecent
blogtags
calendarmonth
calendarupcoming
comments
completionstatus
glossaryrandom
learningplans
newsitems
onlineusers
privatefiles
recentactivity
rssclient
selfcompletion
tags
blog
competency
coursecompletion
mod
privatefiles
assets
core/features
comments
contentlinks
course
fileuploader
h5p
question
reportbuilder
settings
sharedfiles
tag

134
gulp/task-override-lang.js Normal file

@ -0,0 +1,134 @@
// (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.
const gulp = require('gulp');
const slash = require('gulp-slash');
const through = require('through');
const bufferFrom = require('buffer-from');
const pathLib = require('path');
const { readFileSync } = require('fs');
/**
* Use the English generated lang file (src/assets/lang/en.json) to override strings in features lang.json files.
*/
class OverrideLangTask {
/**
* Run the task.
*
* @param done Function to call when done.
*/
run(done) {
const self = this;
const path = pathLib.join('./src/assets/lang', 'en.json');
const data = JSON.parse(readFileSync(path));
const files = {};
for (const key in data) {
let filePath = './src';
const exp = key.split('.');
const type = exp.shift();
let component = 'moodle';
let plainid = exp.shift();
if (exp.length > 0) {
component = plainid;
plainid = exp.join('.');
}
const component_slashes = component.replace('_', '/');
switch (type) {
case 'core':
if (component == 'moodle') {
filePath = pathLib.join(filePath, 'core/lang.json');
} else {
filePath = pathLib.join(filePath, `/core/features/${component_slashes}/lang.json`);
}
break;
case 'addon':
filePath = pathLib.join(filePath, `/addons/${component_slashes}/lang.json`);
break;
case 'assets':
filePath = pathLib.join(filePath, `/${type}/${component}.json`);
break;
default:
filePath = pathLib.join(filePath, `/${type}/lang.json`);
break;
}
filePath = pathLib.resolve(filePath);
if (files[filePath] === undefined) {
files[filePath] = {};
}
files[filePath][plainid] = data[key];
}
const paths = Object.keys(files);
gulp.src(paths, { allowEmpty: true })
.pipe(slash())
.pipe(through(function(destFile) {
const oldContents = self.readFile(destFile);
destFile.contents = self.jsonFile(oldContents, files[destFile.path]);
this.emit('data', destFile);
}))
.pipe(gulp.dest((data) => data.base, { overwrite: true}))
.on('end', done);
}
/**
* Reads file.
*
* @param file File treated.
*/
readFile(file) {
if (file.isNull() || file.isStream()) {
return; // ignore
}
try {
return JSON.parse(file.contents.toString());
} catch (err) {
console.log('Error parsing JSON: ' + err);
}
}
/**
* Creates the stringified json.
*
* @param oldContents File old data.
* @param data File data.
* @return Buffer with the treated data.
*/
jsonFile(oldContents, data) {
data = Object.assign(oldContents, data);
const fileContents = {};
// Force ordering by string key.
Object.keys(data).sort().forEach((key) => {
fileContents[key] = data[key];
});
return bufferFrom(JSON.stringify(fileContents, null, 4) + "\n");
}
}
module.exports = OverrideLangTask;

@ -16,6 +16,7 @@ const BuildLangTask = require('./gulp/task-build-lang');
const BuildBehatPluginTask = require('./gulp/task-build-behat-plugin'); const BuildBehatPluginTask = require('./gulp/task-build-behat-plugin');
const BuildEnvTask = require('./gulp/task-build-env'); const BuildEnvTask = require('./gulp/task-build-env');
const PushTask = require('./gulp/task-push'); const PushTask = require('./gulp/task-push');
const OverrideLangTask = require('./gulp/task-override-lang');
const Utils = require('./gulp/utils'); const Utils = require('./gulp/utils');
const gulp = require('gulp'); const gulp = require('gulp');
@ -36,6 +37,11 @@ gulp.task('lang', (done) => {
new BuildLangTask().run(paths.lang, done); new BuildLangTask().run(paths.lang, done);
}); });
// Use the English generated lang file (src/assets/lang/en.json) to override strings in features lang.json files.
gulp.task('lang-override', (done) => {
new OverrideLangTask().run(done);
});
// Build an env file depending on the current environment. // Build an env file depending on the current environment.
gulp.task('env', (done) => { gulp.task('env', (done) => {
new BuildEnvTask().run(done); new BuildEnvTask().run(done);

@ -10,18 +10,18 @@
"default_lang": "en", "default_lang": "en",
"languages": { "languages": {
"af": "Afrikaans", "af": "Afrikaans",
"ar": "عربي", "ar": "العربية",
"az": "Azərbaycanca", "az": "Azərbaycanca",
"bg": "Български", "bg": "Български",
"ca": "Català", "ca": "Català",
"cs": "Čeština", "cs": "Čeština",
"da": "Dansk", "da": "Dansk",
"de": "Deutsch", "de": "Deutsch",
"de-du": "Deutsch - Du", "de-du": "Deutsch (du)",
"el": "Ελληνικά", "el": "Ελληνικά",
"en": "English", "en": "English",
"en-us": "English - United States", "en-us": "English (United States)",
"es": "Español", "es": "Español - Internacional",
"es-mx": "Español - México", "es-mx": "Español - México",
"eu": "Euskara", "eu": "Euskara",
"fa": "فارسی", "fa": "فارسی",
@ -31,10 +31,10 @@
"he": "עברית", "he": "עברית",
"hi": "हिंदी", "hi": "हिंदी",
"hr": "Hrvatski", "hr": "Hrvatski",
"hsb": "Hornjoserbsski", "hsb": "Hornjoserbsce",
"hu": "magyar", "hu": "magyar",
"hy": "Հայերեն", "hy": "Հայերեն",
"id": "Indonesian", "id": "Bahasa Indonesia",
"it": "Italiano", "it": "Italiano",
"ja": "日本語", "ja": "日本語",
"km": "ខ្មែរ", "km": "ខ្មែរ",
@ -46,7 +46,7 @@
"mn": "Монгол", "mn": "Монгол",
"mr": "मराठी", "mr": "मराठी",
"nl": "Nederlands", "nl": "Nederlands",
"no": "Norsk - bokmål", "no": "Norsk",
"pl": "Polski", "pl": "Polski",
"pt": "Português - Portugal", "pt": "Português - Portugal",
"pt-br": "Português - Brasil", "pt-br": "Português - Brasil",

@ -37,7 +37,10 @@
"ionic:serve:before": "gulp", "ionic:serve:before": "gulp",
"ionic:serve": "cross-env-shell ./scripts/serve.sh", "ionic:serve": "cross-env-shell ./scripts/serve.sh",
"ionic:build:before": "gulp", "ionic:build:before": "gulp",
"postinstall": "patch-package" "postinstall": "patch-package",
"lang:update-langpacks": "./scripts/update_langpacks.sh",
"lang:detect-langpacks": "./scripts/update_langpacks.sh detect",
"lang:create-langindex": "./scripts/create_langindex.sh"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "~10.0.14", "@angular/animations": "~10.0.14",

@ -3,323 +3,22 @@
# Script to create langindex from available language packs. # Script to create langindex from available language packs.
# ./create_langindex.sh [findbetter] # ./create_langindex.sh [findbetter]
# If findbetter is set it will try to find a better solution for every key. # If findbetter is set it will try to find a better solution for every key.
# Edit lang_functions.sh LANGPACKSFOLDER variable to match your system's
# #
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
cd $DIR
source "functions.sh" source "functions.sh"
source "lang_functions.sh" source "lang_functions.sh"
source "create_langindex_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
foundInFile=`grep "string\[\'$id\'\]" $completeFile`
if [ ! -z "$foundInFile" ]; then
coincidence=1
found=$file
return
fi
fi
coincidence=0
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/\{\{/\{}
match=${match/\}\}/\}}
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
else
coincidence=0
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
save_key $key "TBD"
return
fi
do_match " = \'$value\'" "Found some string VALUES for $key in the following paths"
if [ $coincidence -gt 0 ]; then
case=2
save_key $key "TBD"
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
save_key $key "TBD"
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...' print_title 'Generating language from code...'
npx gulp lang npx gulp lang
print_title 'Getting languages' get_english
get_language en
print_title 'Processing file' print_title 'Processing file'
#Create langindex.json if not exists. #Create langindex.json if not exists.
@ -328,7 +27,7 @@ if [ ! -f 'langindex.json' ]; then
fi fi
findbetter=$1 findbetter=$1
parse_file '../src/assets/lang/en.json' $findbetter parse_file $findbetter
echo echo

@ -0,0 +1,355 @@
#!/bin/bash
#
# Functions used to create langidex.
#
SERVER_URL='https://download.moodle.org/'
# Downloads a file and if it's a zip file, unzip it.
function download_file {
local url=$1
local filename=$(basename ${url})
pushd $LANGPACKS_PATH > /dev/null
curl -s $url --output $filename > /dev/null
size=$(du -k "$filename" | cut -f 1)
if [ ! -n $filename ] || [ $size -le 1 ]; then
echo "Wrong or corrupt file $filename"
rm $filename
popd > /dev/null
return
fi
if [[ $filename == *.zip ]]; then
local lang="${filename%.*}"
# Delete previous downloaded folder
rm -R $lang > /dev/null 2>&1> /dev/null
# Unzip
unzip -o -u $lang.zip > /dev/null
# Delete the zip
rm $filename
fi
popd > /dev/null
}
function get_english {
if [ ! -d $LANGPACKS_PATH ]; then
mkdir $LANGPACKS_PATH
fi
get_app_version
echo "Getting English language..."
download_file "$SERVER_URL/download.php/direct/langpack/$LANGVERSION/en.zip"
}
#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 a lang id 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="$LANGPACKS_PATH/en/$file.php"
if [ -f "$completeFile" ]; then
foundInFile=`grep "string\[\'$id\'\]" $completeFile`
if [ ! -z "$foundInFile" ]; then
coincidence=1
found=$file
return
fi
fi
coincidence=0
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/\{\{/\{}
match=${match/\}\}/\}}
filematch=""
coincidence=`grep "$match" $LANGPACKS_PATH/en/*.php | wc -l`
if [ $coincidence -eq 1 ]; then
filematch=`grep "$match" $LANGPACKS_PATH/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" $LANGPACKS_PATH/en/*.php
else
coincidence=0
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
save_key $key "TBD"
return
fi
do_match " = \'$value\'" "Found some string VALUES for $key in the following paths"
if [ $coincidence -gt 0 ]; then
case=2
save_key $key "TBD"
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
save_key $key "TBD"
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" $LANGPACKS_PATH/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 {
file="$LANG_PATH/en.json"
findbetter=$1
keys=`jq -r 'keys[]' $file`
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
}

@ -1,541 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Helper functions converting moodle strings to json.
*/
function detect_languages($languages) {
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 = [];
foreach ($detect_lang as $lang) {
$new = detect_lang($lang);
if ($new) {
$new_langs[$lang] = $lang;
}
}
return $new_langs;
}
function build_languages($languages, $added_langs = []) {
// Process the languages.
foreach ($languages as $lang) {
if (build_lang($lang)) {
$added_langs[$lang] = $lang;
}
}
return $added_langs;
}
/**
* Loads lang index keys.
*/
function load_langindex() {
global $STATS;
global $LANGINDEX;
$local = 0;
$total = 0;
// Process the index file, just once.
$langindexjson = load_json('langindex.json');
$LANGINDEX = [];
foreach ($langindexjson as $appkey => $value) {
if ($value == APPMODULENAME) {
$file = $value;
$lmskey = $appkey;
$local++;
} else {
$exp = explode('/', $value, 2);
$file = $exp[0];
if (count($exp) == 2) {
$lmskey = $exp[1];
} else {
$exp = explode('.', $appkey, 3);
if (count($exp) == 3) {
$lmskey = $exp[2];
} else {
$lmskey = $exp[1];
}
}
}
if (!isset($LANGINDEX[$file])) {
$LANGINDEX[$file] = [];
}
$LANGINDEX[$file][$appkey] = $lmskey;
$total++;
}
$STATS = new StdClass();
$STATS->local = $local;
$STATS->total = $total;
echo "Total strings to translate $total ($local local)\n";
}
/**
* Add lang names to config file.
*
* @param $langs Array of language codes to add.
* @param $config Loaded config file.
*/
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 = get_langfolder($lang);
$lmsstring = get_translation_strings($langfoldername, 'langconfig');
$config['languages']->$lang = $lmsstring['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 );
save_json(CONFIG, $config);
}
}
/**
* Save json data.
*
* @param $path Path of the file to load.
* @param $content Content string to save.
*/
function save_json($path, $content) {
file_put_contents($path, str_replace('\/', '/', json_encode($content, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT))."\n");
}
/**
* Load json data.
*
* @param $path Path of the file to load.
* @return Associative array obtained from json.
*/
function load_json($path) {
$file = file_get_contents($path);
return (array) json_decode($file);
}
/**
* Get's lang folder from lang code.
*
* @param $lang Lang code.
* @return Folder path.
*/
function get_langfolder($lang) {
$folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang);
if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) {
return false;
}
return $folder;
}
/**
* Import translation file from langpack and returns it.
*
* @param $langfoldername Lang folder path.
* @param $file File name (excluding extension).
* @param $override_folder If needed, the folder of the file to override strings.
* @return String array.
*/
function get_translation_strings($langfoldername, $file, $override_folder = false) {
$lmsstring = import_translation_strings($langfoldername, $file);
if ($override_folder) {
$override = import_translation_strings($override_folder, $file);
$lmsstring = array_merge($lmsstring, $override);
}
return $lmsstring;
}
/**
* Import translation file from langpack and returns it.
*
* @param $langfoldername Lang folder path.
* @param $file File name (excluding extension).
* @return String array.
*/
function import_translation_strings($langfoldername, $file) {
$path = $langfoldername.'/'.$file.'.php';
// Apply translations.
if (!file_exists($path)) {
return [];
}
$string = [];
include($path);
return $string;
}
/**
* Build translations files from langpack.
*
* @param lang Language code.
* @return Wether it succeeded.
*/
function build_lang($lang) {
global $STATS;
global $LANGINDEX;
$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 = $STATS->total;
$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;
if (file_exists(ASSETSPATH.$lang.'.json')) {
// Load lang files just once.
$langFile = load_json(ASSETSPATH.$lang.'.json');
}
$translations = [];
// Add the translation to the array.
foreach ($LANGINDEX as $file => $keys) {
$lmsstring = get_translation_strings($langfoldername, $file, $override_langfolder);
foreach ($keys as $appkey => $lmskey) {
// Apply translations.
if (empty($lmsstring)) {
if ($file == 'donottranslate') {
// Restore it form the json.
if ($langFile && is_array($langFile) && isset($langFile[$appkey])) {
$translations[$appkey] = $langFile[$appkey];
} else {
// If not present, do not count it in the total.
$total--;
}
continue;
}
if (TOTRANSLATE) {
echo "\n\t\tTo translate $lmskey on $file";
}
continue;
}
if (!isset($lmsstring[$lmskey]) || ($lang == 'en' && $file == APPMODULENAME)) {
// Not yet translated. Do not override.
if ($langFile && is_array($langFile) && isset($langFile[$appkey])) {
$translations[$appkey] = $langFile[$appkey];
if ($file == APPMODULENAME) {
$local++;
}
}
if (TOTRANSLATE && !isset($lmsstring[$lmskey])) {
echo "\n\t\tTo translate $lmskey on $file";
}
continue;
}
$text = $lmsstring[$lmskey];
if ($file != APPMODULENAME) {
$text = str_replace('$a->@', '$a.', $text);
$text = str_replace('$a->', '$a.', $text);
$text = str_replace('{$a', '{{$a', $text);
$text = str_replace('}', '}}', $text);
$text = preg_replace('/@@.+?@@(<br>)?\\s*/', '', $text);
// Prevent double.
$text = str_replace(['{{{', '}}}'], ['{{', '}}'], $text);
} else {
// @TODO: Remove that line when core.cannotconnect and core.login.invalidmoodleversion are completelly changed to use $a
if (($appkey == 'core.cannotconnect' || $appkey == 'core.login.invalidmoodleversion') && strpos($text, '2.4')) {
$text = str_replace('2.4', '{{$a}}', $text);
}
$local++;
}
$translations[$appkey] = 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);
save_json(ASSETSPATH.$lang.'.json', $translations);
$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_module_file($LANGINDEX[APPMODULENAME], $translations);
override_component_lang_files($translations);
}
return true;
}
/**
* Generates an ASCII progress bar.
*
* @param $percentage Done part.
* @param $length Length of the text.
* @return Text generated.
*/
function progressbar($percentage, $length = 10) {
$done = floor($percentage / $length);
return "\t".str_repeat('=', $done) . str_repeat('-', $length - $done);
}
/**
* Check translations on langpack and detects if the language should be added.
*
* @param lang Language code.
* @return If the file should be added to the app.
*/
function detect_lang($lang) {
global $STATS;
global $LANGINDEX;
$langfoldername = get_langfolder($lang);
if (!$langfoldername) {
echo "Cannot translate $lang, folder not found";
return false;
}
$total = $STATS->total;
$success = 0;
$local = 0;
$lmsstring = get_translation_strings($langfoldername, 'langconfig');
$parent = isset($lmsstring['parentlanguage']) ? $lmsstring['parentlanguage'] : "";
if (!isset($lmsstring['thislanguage'])) {
echo "Cannot translate $lang, translated name not found";
return false;
}
$title = $lang;
if ($parent != "" && $parent != $lang) {
$title .= " ($parent)";
}
$langname = $lmsstring['thislanguage'];
$title .= " ".$langname." -D";
$lmsstring = get_translation_strings($langfoldername, APPMODULENAME);
if (!empty($lmsstring)) {
// Add the translation to the array.
foreach ($LANGINDEX as $file => $keys) {
$lmsstring = get_translation_strings($langfoldername, $file);
// Apply translations.
if (empty($lmsstring)) {
// Do not count non translatable in the totals.
if ($file == 'donottranslate') {
$total -= count($keys);
}
continue;
}
foreach ($keys as $lmskey) {
if (!isset($lmsstring[$lmskey])) {
continue;
}
if ($file == APPMODULENAME) {
$local++;
}
$success++;
}
}
}
echo "Checking ".$title.str_repeat("\t", 7 - floor(mb_strlen($title, 'UTF-8')/8));
if ($local == 0) {
echo "\tNo Mobile App strings found\n";
} else {
$percentage = floor($success/$total * 100);
$bar = progressbar($percentage);
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;
}
/**
* Save a key - value pair into a json file.
*
* @param key Key of the json object.
* @param value Value of the json object.
* @param filePath Path of the json file.
*/
function save_key($key, $value, $filePath) {
$file = load_json($filePath);
$value = html_entity_decode($value);
if (!isset($file[$key]) || $file[$key] != $value) {
$file[$key] = $value;
ksort($file);
save_json($filePath, $file);
}
}
/**
* Take newer ENGLISH translations from the langpacks and applies it to the app lang.json files.
*
* @param [array] $translations English translations.
*/
function override_component_lang_files($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':
if ($component == 'moodle') {
$path .= 'core/lang.json';
} else {
$path .= 'core/features/'.str_replace('_', '/', $component).'/lang.json';
}
break;
case 'addon':
$path .= 'addons/'.str_replace('_', '/', $component).'/lang.json';
break;
case 'assets':
$path .= $type.'/'.$component.'.json';
break;
default:
$path .= $type.'/lang.json';
break;
}
if (is_file($path)) {
save_key($plainid, $value, $path);
} else {
echo "Cannot override: $path not found.\n";
}
}
}
/**
* Generates local module file to update languages in AMOS.
*
* @param [array] $appindex Translation appindex.
* @param [array] $translations English translations.
*/
function generate_local_module_file($appindex, $translations) {
echo 'Generate '.APPMODULENAME."\n";
$lmsstring = "";
foreach ($appindex as $appkey => $lmskey) {
if (isset($translations[$appkey])) {
$lmsstring .= '$string[\''.$appkey.'\'] = \''.str_replace("'", "\'", $translations[$appkey]).'\';'."\n";
}
}
if (empty($lmsstring)) {
echo "ERROR, translations not found, you probably didn't run gulp lang!\n";
return;
}
$filepath = '../../moodle-'.APPMODULENAME.'/lang/en/'.APPMODULENAME.'.php';
$filecontents = file_get_contents($filepath);
$startcomment = "/* AUTO START */\n";
$endcomment = '/* AUTO END */';
$start = strpos($filecontents, $startcomment);
$start = $start === false ? 0 : $start + strlen($startcomment);
$end = strpos($filecontents, $endcomment, $start);
$end = $end === false ? strlen($filecontents) : $end;
$filecontents = substr_replace($filecontents, $lmsstring, $start, $end - $start);
if (substr($filecontents, -2) != "\n\n") {
$filecontents .= "\n";
}
file_put_contents($filepath, $filecontents);
}

156
scripts/lang_functions.sh Executable file → Normal file

@ -1,49 +1,15 @@
#!/bin/bash #!/bin/bash
# #
# Functions to fetch languages. # Common functions to fetch languages.
# #
LANGPACKSFOLDER='../../moodle-langpacks' # Langpacks will be downloaded here. LANG_PATH='../src/assets/lang'
BUCKET='moodle-lang-prod' SUFFIX='' # Determines suffix of the langpacks to be merged. Ie, using wp will include en.json and en_wp.json
MOODLEORG_URL='https://download.moodle.org/download.php/direct/langpack' # (and the later will override the earlier).
DEFAULT_LASTVERSION='4.2' # Update it every version. LANGPACKS_PATH='/tmp/moodleapp-lang'
# Checks if AWS is available and configured. # Get the version of the Moodle App to fetch latest languages.
function check_aws { function get_app_version {
if [ ! -z $AWS_SERVICE ]; then
return
fi
export AWS_SERVICE=1
aws --version &> /dev/null
if [ $? -ne 0 ]; then
export AWS_SERVICE=0
echo 'AWS not installed. Check https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html for more info.'
return
fi
# In order to login to AWS, use credentials file or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY vars.
if [ ! -f ~/.aws/credentials ] && ([ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]); then
export AWS_SERVICE=0
echo 'AWS Cannot authenticate. Use aws configure or set the proper env vars.'
return
fi
}
function list_aws_files {
local folder="$1"
check_aws
if [ $AWS_SERVICE -eq 1 ]; then
export AWS_FOLDERS=`aws s3 ls s3://$BUCKET/$1`
else
export AWS_FOLDERS=[]
fi
}
# Get last version of Moodle to fetch latest languages.
function get_lang_version {
if [ ! -z "${LANGVERSION}" ]; then if [ ! -z "${LANGVERSION}" ]; then
return return
fi fi
@ -55,110 +21,6 @@ function get_lang_version {
return return
fi fi
list_aws_files '' echo "Cannot decide version"
LANGVERSION='' exit 1
for folder in $AWS_FOLDERS; do
if [ $folder != 'PRE' ]; then
LANGVERSION=${folder/\//}
fi
done
if [ ! -z "${LANGVERSION}" ]; then
echo "Using last version $LANGVERSION detected"
return
fi
LANGVERSION=$DEFAULT_LASTVERSION
echo "Using default version $LANGVERSION"
}
# Create langfolder
function create_langfolder {
if [ ! -d $LANGPACKSFOLDER ]; then
mkdir $LANGPACKSFOLDER
fi
}
# Get language list from the installed ones (will not discover new translations).
function get_language_folders {
list_aws_files "$LANGVERSION/"
langs=""
for file in $AWS_FOLDERS; do
if [[ "$file" == *.zip ]]; then
file=${file/\.zip/}
langs+="$file "
fi
done
if [ -z "${langs}" ]; then
# Get language list from the installed ones (will not discover new translations).
echo "Fallback language list will only get current installation languages"
langs=`jq -r '.languages | keys[]' ../moodle.config.json`
fi
}
# Entry function to get a language file.
function get_language {
lang=$1
lang=${lang/-/_}
get_lang_version
create_langfolder
echo "Getting $lang language"
pushd $LANGPACKSFOLDER > /dev/null
curl -s $MOODLEORG_URL/$LANGVERSION/$lang.zip --output $lang.zip > /dev/null
size=$(du -k "$lang.zip" | cut -f 1)
if [ ! -n $lang.zip ] || [ $size -le 1 ]; then
echo "Wrong language name or corrupt file for $lang"
rm $lang.zip
popd > /dev/null
return
fi
rm -R $lang > /dev/null 2>&1> /dev/null
unzip -o -u $lang.zip > /dev/null
# This is the AWS version to get the language but right now it's slower.
# aws s3 cp s3://$BUCKET/$LANGVERSION/$lang.zip . > /dev/null
rm $lang.zip
popd > /dev/null
}
# Entry function to get all language files.
function get_languages {
suffix=$1
if [ -z $suffix ]; then
suffix=''
fi
get_lang_version
if [ -d $LANGPACKSFOLDER ]; then
lastupdate=`date -r $LANGPACKSFOLDER +%s`
currenttime=`date +%s`
ellapsedtime=$((currenttime - lastupdate))
if [ $ellapsedtime -lt 3600 ]; then
echo 'Recently updated, skip update languages'
return
fi
else
create_langfolder
fi
get_language_folders
for lang in $langs; do
get_language "$lang"
if [ ! -z $suffix ]; then
get_language "$lang$suffix"
fi
done
} }

@ -1,68 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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('APPMODULENAME','local_moodlemobileapp');
define('ASSETSPATH', '../src/assets/lang/');
define('CONFIG', '../moodle.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;
}
if (!file_exists(ASSETSPATH)) {
mkdir(ASSETSPATH);
}
load_langindex();
$added_langs = build_languages($languages);
if ($forcedetect) {
$new_langs = detect_languages($languages);
if (!empty($new_langs)) {
echo "\n\n\nThe following languages are going to be added\n\n\n";
$added_langs = build_languages($new_langs, $added_langs);
}
}
add_langs_to_config($added_langs, $config);

@ -1,29 +0,0 @@
#!/bin/bash
#
# Script to update language packs on assets and detect new translated languages.
# ./update_lang.sh [language]
# If language is set it will only update the selected language.
# Edit lang_functions.sh LANGPACKSFOLDER variable to match your system's
#
source "functions.sh"
source "lang_functions.sh"
forceLang=$1
print_title 'Generating language from code...'
npx gulp lang
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
get_languages
php -f moodle_to_json.php
else
get_language "$forceLang"
php -f moodle_to_json.php "$forceLang"
fi
cp langindex.json ../../moodle-local_moodlemobileapp
print_ok 'All done!'

186
scripts/update_lang_functions.sh Executable file

@ -0,0 +1,186 @@
#!/bin/bash
#
# Functions to update langpacks.
#
APPMODULENAME='local_moodlemobileapp'
TOTAL_STRINGS=0
LANGINDEX_STRINGS=0
function progressbar {
let _progress=(${1}*100/100*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
_fill=$(printf "%${_done}s")
_empty=$(printf "%${_left}s")
bar=`printf "[${_fill// /#}${_empty// /-}] ${_progress}%%"`
}
# Copy language file
function copy_lang {
lang=$1
index_keys=`jq -r 'to_entries[] | "\"\(.key)\","' langindex.json`
index_keys=`echo $index_keys | sed 's/,*$//'`
hyphenlang=${lang/_/-}
langfilepath=$LANG_PATH/$hyphenlang.json
cp $LANGPACKS_PATH/$lang.json $langfilepath
# Merge SUFFIX file if exists.
if [ ! -z "$SUFFIX" ] && [ -f "$LANGPACKS_PATH/${lang}_${SUFFIX}.json" ]; then
suffixfilepath="$LANGPACKS_PATH/${lang}_${SUFFIX}.json"
jq --indent 4 -s --sort-keys ".[0] + .[1]" $langfilepath $suffixfilepath > /tmp/moodle-langtmp.json
mv /tmp/moodle-langtmp.json $langfilepath
fi
# Remove strings non exiting on langindex.
query="with_entries(select([.key] | inside([$index_keys])))"
jq --indent 2 -r "$query" $langfilepath > /tmp/moodle-langtmp.json
mv /tmp/moodle-langtmp.json $langfilepath
name=`jq -r .\"$lang\".name $LANGPACKS_PATH/languages.json`
local=`jq -r .\"$lang\".local $LANGPACKS_PATH/languages.json`
translated=`jq -r '. | length' $langfilepath`
percentage=`echo "($translated * 100) /$LANGINDEX_STRINGS" | bc`
progressbar $percentage
echo -e "Generated $hyphenlang\t $translated of $LANGINDEX_STRINGS\t $bar ($local local)"
# Add or update language name to config.
newlang="{\"$hyphenlang\": \"$name\"}"
languages=`jq -s --sort-keys ".[0].languages + $newlang" ../moodle.config.json`
jq --indent 4 -s ".[0].languages = $languages | .[0]" ../moodle.config.json > /tmp/moodle-langtmp.json
mv /tmp/moodle-langtmp.json ../moodle.config.json
}
function detect_lang {
lang=$1
name=`jq -r .\"$lang\".name $LANGPACKS_PATH/languages.json`
if [ -z "$name" ] || [ "$name" == 'null' ]; then
continue
fi
hyphenlang=${lang/_/-}
if [ -f $LANG_PATH/$hyphenlang.json ]; then
# Already exists
continue
fi
local=`jq -r .\"$lang\".local $LANGPACKS_PATH/languages.json`
translated=`jq -r .\"$lang\".translated $LANGPACKS_PATH/languages.json`
percentage=`echo "($translated * 100) /$TOTAL_STRINGS" | bc`
progressbar $percentage
echo -e "Checking $lang\t $translated of $TOTAL_STRINGS \t $bar ($local local)";
if [[ ( $percentage -gt 75 && $local -gt 50 ) || ( $percentage -gt 50 && $local -gt 75 ) ]] ; then
name=`jq -r .\"$lang\".name $LANGPACKS_PATH/languages.json`
echo "*** NEW LANGUAGE DETECTED $lang - $name ***"
copy_lang $lang
fi
}
function load_langpacks {
get_app_version
print_title 'Getting local mobile langs'
if [ -d $LANGPACKS_PATH ]; then
pushd $LANGPACKS_PATH
git checkout "langpack_$LANGVERSION"
git pull
if [ $? -ne 0 ]; then
echo "Cannot update language repository"
exit 1
fi
popd
else
git clone --depth 1 --single-branch --branch "langpack_$LANGVERSION" https://github.com/moodlehq/moodle-local_moodlemobileapp.git $LANGPACKS_PATH
if [ $? -ne 0 ]; then
echo "Cannot clone language repository"
exit 1
fi
fi
local_strings=`jq -r '.languages.local' $LANGPACKS_PATH/languages.json`
TOTAL_STRINGS=`jq -r '.languages.total' $LANGPACKS_PATH/languages.json`
LANGINDEX_STRINGS=`jq -r '. | length' langindex.json`
print_message "Total strings to translate $TOTAL_STRINGS ($local_strings local)";
}
# Entry function to get all language files.
function get_languages {
print_title 'Copying existing languages'
# Existing languages, copy and clean the files.
langs=`jq -r '.languages | keys[]' ../moodle.config.json`
for lang in $langs; do
lang=${lang//-/_}
copy_lang $lang
done
}
# Entry function to detect new languages.
function detect_languages {
# Do not detect new langs when suffix is set.
if [ ! -z $SUFFIX ]; then
return
fi
print_title "Detect new languages"
langs=`jq -r 'keys[]' $LANGPACKS_PATH/languages.json`
for lang in $langs; do
if [[ $lang = *_wp ]]; then
# Skip Workplace.
continue
fi
detect_lang $lang
done
}
# Entry function to generate translation module file.
function generate_local_module_file {
if [ ! -d "../../moodle-$APPMODULENAME" ]; then
print_error "Module $APPMODULENAME directory does not exists, skipping..."
return
fi
print_title "Generating $APPMODULENAME..."
module_translations=''
keys=`jq -r 'map_values(select(contains("local_moodlemobileapp"))) | keys[]' langindex.json`
for key in $keys; do
# Check if already parsed.
translation=`jq -r .\"$key\" $LANG_PATH/en.json`
if [ -z "$translation" ]; then
echo "Key $key not translated!"
continue
fi
translation="${translation//\'/\\\'}"
module_translations="$module_translations\$string['$key'] = '$translation';\n";
done
if [ -z "$module_translations" ]; then
print_error "ERROR, translations not found, you probably didn't run gulp lang!";
return
fi
echo -e $module_translations > /tmp/translations.php
filepath="../../moodle-$APPMODULENAME/lang/en/$APPMODULENAME.php";
BEGIN_GEN=$(cat $filepath | grep -n '\/\* AUTO START \*\/' | sed 's/\(.*\):.*/\1/g')
END_GEN=$(cat $filepath | grep -n '\/\* AUTO END \*\/' | sed 's/\(.*\):.*/\1/g')
cat <(head -n $BEGIN_GEN $filepath) /tmp/translations.php <(tail -n +$END_GEN $filepath) > /tmp/translations_temp.php
mv /tmp/translations_temp.php $filepath
cp langindex.json ../../moodle-$APPMODULENAME
}

36
scripts/update_langpacks.sh Executable file

@ -0,0 +1,36 @@
#!/bin/bash
#
# Script to update language packs on assets and detect new translated languages.
# ./update_langpacks.sh [detect]
#
# When detect is present, it will check other languages not included in moodle.config.json to be included in the build.
# It will alo generate the local module files and override the current lang.json on the src folder.
#
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
cd $DIR
source "functions.sh"
source "lang_functions.sh"
source "update_lang_functions.sh"
load_langpacks
get_languages
if [[ -z $1 ]]; then
print_ok 'All done!'
exit 0
fi
# Detect new languages and copy langindex to the translations folder.
detect_languages
generate_local_module_file
gulp lang-override
print_ok 'All done!'

@ -1,3 +1,3 @@
{ {
"pluginname": "Activity results" "pluginname": "Activity results"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Latest badges" "pluginname": "Latest badges"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Blog menu" "pluginname": "Blog menu"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Recent blog entries" "pluginname": "Recent blog entries"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Blog tags" "pluginname": "Blog tags"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Calendar" "pluginname": "Calendar"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Upcoming events" "pluginname": "Upcoming events"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Comments" "pluginname": "Comments"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Course completion status" "pluginname": "Course completion status"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Random glossary entry" "pluginname": "Random glossary entry"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Learning plans" "pluginname": "Learning plans"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Latest announcements" "pluginname": "Latest announcements"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Online users" "pluginname": "Online users"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Private files" "pluginname": "Private files"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Recent activity" "pluginname": "Recent activity"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Remote RSS feeds" "pluginname": "Remote RSS feeds"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Self completion" "pluginname": "Self completion"
} }

@ -1,3 +1,3 @@
{ {
"pluginname": "Tags" "pluginname": "Tags"
} }

@ -9,4 +9,4 @@
"publishtoworld": "Anyone in the world", "publishtoworld": "Anyone in the world",
"showonlyyourentries": "Show only your entries", "showonlyyourentries": "Show only your entries",
"siteblogheading": "Site blog" "siteblogheading": "Site blog"
} }

@ -47,4 +47,4 @@
"userplans": "Learning plans", "userplans": "Learning plans",
"xcompetenciesproficientoutofy": "{{$a.x}} out of {{$a.y}} competencies are proficient", "xcompetenciesproficientoutofy": "{{$a.x}} out of {{$a.y}} competencies are proficient",
"xcompetenciesproficientoutofyincourse": "You are proficient in {{$a.x}} out of {{$a.y}} competencies in this course." "xcompetenciesproficientoutofyincourse": "You are proficient in {{$a.x}} out of {{$a.y}} competencies in this course."
} }

@ -20,4 +20,4 @@
"requirement": "Requirement", "requirement": "Requirement",
"status": "Status", "status": "Status",
"viewcoursereport": "View course report" "viewcoursereport": "View course report"
} }

@ -25,4 +25,4 @@
"savemychoice": "Save my choice", "savemychoice": "Save my choice",
"userchoosethisoption": "Users who chose this option", "userchoosethisoption": "Users who chose this option",
"yourselection": "Your selection" "yourselection": "Your selection"
} }

@ -1,4 +1,4 @@
{ {
"emptyfilelist": "There are no files to show.", "emptyfilelist": "There are no files to show.",
"modulenameplural": "Folders" "modulenameplural": "Folders"
} }

@ -3,4 +3,4 @@
"modulenameplural": "IMS content packages", "modulenameplural": "IMS content packages",
"showmoduledescription": "Show description", "showmoduledescription": "Show description",
"toc": "TOC" "toc": "TOC"
} }

@ -83,4 +83,4 @@
"youranswer": "Your answer", "youranswer": "Your answer",
"yourcurrentgradeisoutof": "Your current grade is {{$a.grade}} out of {{$a.total}}", "yourcurrentgradeisoutof": "Your current grade is {{$a.grade}} out of {{$a.total}}",
"youshouldview": "You should answer at least: {{$a}}" "youshouldview": "You should answer at least: {{$a}}"
} }

@ -3,4 +3,4 @@
"errorinvalidlaunchurl": "The launch URL is not valid.", "errorinvalidlaunchurl": "The launch URL is not valid.",
"launchactivity": "Launch the activity", "launchactivity": "Launch the activity",
"modulenameplural": "External tools" "modulenameplural": "External tools"
} }

@ -1,4 +1,4 @@
{ {
"errorwhileloadingthepage": "Error while loading the page content.", "errorwhileloadingthepage": "Error while loading the page content.",
"modulenameplural": "Pages" "modulenameplural": "Pages"
} }

@ -7,4 +7,4 @@
"responses": "Responses", "responses": "Responses",
"results": "Results", "results": "Results",
"surveycompletednograph": "You have completed this survey." "surveycompletednograph": "You have completed this survey."
} }

@ -2,4 +2,4 @@
"accessurl": "Access the URL", "accessurl": "Access the URL",
"modulenameplural": "URLs", "modulenameplural": "URLs",
"pointingtourl": "URL that the resource points to." "pointingtourl": "URL that the resource points to."
} }

@ -5,4 +5,4 @@
"files": "Files", "files": "Files",
"privatefiles": "Private files", "privatefiles": "Private files",
"sitefiles": "Site files" "sitefiles": "Site files"
} }

@ -248,4 +248,4 @@
"ZA": "South Africa", "ZA": "South Africa",
"ZM": "Zambia", "ZM": "Zambia",
"ZW": "Zimbabwe" "ZW": "Zimbabwe"
} }

@ -1,71 +1,71 @@
{ {
"application/dash_xml": "Dynamic Adaptive Streaming over HTTP (MPEG-DASH)", "application/dash_xml": "Dynamic Adaptive Streaming over HTTP (MPEG-DASH)",
"application/epub_zip": "EPUB ebook", "application/epub_zip": "EPUB ebook",
"application/json": "{{$a.MIMETYPE2}} text", "application/json": "{{$a.MIMETYPE2}} text",
"application/msword": "Word document", "application/msword": "Word document",
"application/pdf": "PDF document", "application/pdf": "PDF document",
"application/vnd.google-apps.audio": "Google Drive audio", "application/vnd.google-apps.audio": "Google Drive audio",
"application/vnd.google-apps.document": "Google Docs", "application/vnd.google-apps.document": "Google Docs",
"application/vnd.google-apps.drawing": "Google Drawing", "application/vnd.google-apps.drawing": "Google Drawing",
"application/vnd.google-apps.file": "Google Drive file", "application/vnd.google-apps.file": "Google Drive file",
"application/vnd.google-apps.folder": "Google Drive folder", "application/vnd.google-apps.folder": "Google Drive folder",
"application/vnd.google-apps.form": "Google Forms", "application/vnd.google-apps.form": "Google Forms",
"application/vnd.google-apps.fusiontable": "Google Fusion Tables", "application/vnd.google-apps.fusiontable": "Google Fusion Tables",
"application/vnd.google-apps.presentation": "Google Slides", "application/vnd.google-apps.presentation": "Google Slides",
"application/vnd.google-apps.script": "Google Apps Scripts", "application/vnd.google-apps.script": "Google Apps Scripts",
"application/vnd.google-apps.site": "Google Sites", "application/vnd.google-apps.site": "Google Sites",
"application/vnd.google-apps.spreadsheet": "Google Sheets", "application/vnd.google-apps.spreadsheet": "Google Sheets",
"application/vnd.google-apps.video": "Google Drive video", "application/vnd.google-apps.video": "Google Drive video",
"application/vnd.moodle.backup": "Moodle backup", "application/vnd.moodle.backup": "Moodle backup",
"application/vnd.ms-excel": "Excel spreadsheet", "application/vnd.ms-excel": "Excel spreadsheet",
"application/vnd.ms-excel.sheet.macroEnabled.12": "Excel 2007 macro-enabled workbook", "application/vnd.ms-excel.sheet.macroEnabled.12": "Excel 2007 macro-enabled workbook",
"application/vnd.ms-powerpoint": "Powerpoint presentation", "application/vnd.ms-powerpoint": "Powerpoint presentation",
"application/vnd.oasis.opendocument.spreadsheet": "OpenDocument Spreadsheet", "application/vnd.oasis.opendocument.spreadsheet": "OpenDocument Spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-template": "OpenDocument Spreadsheet template", "application/vnd.oasis.opendocument.spreadsheet-template": "OpenDocument Spreadsheet template",
"application/vnd.oasis.opendocument.text": "OpenDocument Text document", "application/vnd.oasis.opendocument.text": "OpenDocument Text document",
"application/vnd.oasis.opendocument.text-template": "OpenDocument Text template", "application/vnd.oasis.opendocument.text-template": "OpenDocument Text template",
"application/vnd.oasis.opendocument.text-web": "OpenDocument Web page template", "application/vnd.oasis.opendocument.text-web": "OpenDocument Web page template",
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "Powerpoint 2007 presentation", "application/vnd.openxmlformats-officedocument.presentationml.presentation": "Powerpoint 2007 presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow": "Powerpoint 2007 slideshow", "application/vnd.openxmlformats-officedocument.presentationml.slideshow": "Powerpoint 2007 slideshow",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel 2007 spreadsheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel 2007 spreadsheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Excel 2007 template", "application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Excel 2007 template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word 2007 document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word 2007 document",
"application/x-iwork-keynote-sffkey": "iWork Keynote presentation", "application/x-iwork-keynote-sffkey": "iWork Keynote presentation",
"application/x-iwork-numbers-sffnumbers": "iWork Numbers spreadsheet", "application/x-iwork-numbers-sffnumbers": "iWork Numbers spreadsheet",
"application/x-iwork-pages-sffpages": "iWork Pages document", "application/x-iwork-pages-sffpages": "iWork Pages document",
"application/x-javascript": "JavaScript source", "application/x-javascript": "JavaScript source",
"application/x-mpegURL": "HTTP Live Streaming (HLS)", "application/x-mpegURL": "HTTP Live Streaming (HLS)",
"application/x-mspublisher": "Publisher document", "application/x-mspublisher": "Publisher document",
"application/x-shockwave-flash": "Flash animation", "application/x-shockwave-flash": "Flash animation",
"application/xhtml_xml": "XHTML document", "application/xhtml_xml": "XHTML document",
"archive": "Archive ({{$a.EXT}})", "archive": "Archive ({{$a.EXT}})",
"audio": "Audio file ({{$a.EXT}})", "audio": "Audio file ({{$a.EXT}})",
"default": "{{$a.mimetype}}", "default": "{{$a.mimetype}}",
"document/unknown": "File", "document/unknown": "File",
"group:archive": "Archive files", "group:archive": "Archive files",
"group:audio": "Audio files", "group:audio": "Audio files",
"group:document": "Document files", "group:document": "Document files",
"group:html_audio": "Audio files natively supported by browsers", "group:html_audio": "Audio files natively supported by browsers",
"group:html_track": "HTML track files", "group:html_track": "HTML track files",
"group:html_video": "Video files natively supported by browsers", "group:html_video": "Video files natively supported by browsers",
"group:image": "Image files", "group:image": "Image files",
"group:media_source": "Streaming media", "group:media_source": "Streaming media",
"group:optimised_image": "Image files to be optimised, such as badges", "group:optimised_image": "Image files to be optimised, such as badges",
"group:presentation": "Presentation files", "group:presentation": "Presentation files",
"group:sourcecode": "Source code", "group:sourcecode": "Source code",
"group:spreadsheet": "Spreadsheet files", "group:spreadsheet": "Spreadsheet files",
"group:video": "Video files", "group:video": "Video files",
"group:web_audio": "Audio files used on the web", "group:web_audio": "Audio files used on the web",
"group:web_file": "Web files", "group:web_file": "Web files",
"group:web_image": "Image files used on the web", "group:web_image": "Image files used on the web",
"group:web_video": "Video files used on the web", "group:web_video": "Video files used on the web",
"image": "Image ({{$a.MIMETYPE2}})", "image": "Image ({{$a.MIMETYPE2}})",
"image/vnd.microsoft.icon": "Windows icon", "image/vnd.microsoft.icon": "Windows icon",
"text/css": "Cascading Style-Sheet", "text/css": "Cascading Style-Sheet",
"text/csv": "Comma-separated values", "text/csv": "Comma-separated values",
"text/html": "HTML document", "text/html": "HTML document",
"text/plain": "Text file", "text/plain": "Text file",
"text/rtf": "RTF document", "text/rtf": "RTF document",
"text/vtt": "Web Video Text Track", "text/vtt": "Web Video Text Track",
"video": "Video file ({{$a.EXT}})" "video": "Video file ({{$a.EXT}})"
} }

@ -9,4 +9,4 @@
"nocomments": "No comments", "nocomments": "No comments",
"savecomment": "Save comment", "savecomment": "Save comment",
"warningcommentsnotsent": "Couldn't sync comments. {{error}}" "warningcommentsnotsent": "Couldn't sync comments. {{error}}"
} }

@ -5,4 +5,4 @@
"errornoactions": "Couldn't find an action to perform with this link.", "errornoactions": "Couldn't find an action to perform with this link.",
"errornosites": "Couldn't find any site to handle this link.", "errornosites": "Couldn't find any site to handle this link.",
"errorredirectothersite": "The redirect URL cannot point to a different site." "errorredirectothersite": "The redirect URL cannot point to a different site."
} }

@ -3,7 +3,7 @@
"activitynotyetviewableremoteaddon": "Your organisation installed a plugin that is not yet supported.", "activitynotyetviewableremoteaddon": "Your organisation installed a plugin that is not yet supported.",
"allsections": "All sections", "allsections": "All sections",
"aria:sectionprogress": "Section progress:", "aria:sectionprogress": "Section progress:",
"availablespace": " You currently have about {{available}} free space.", "availablespace": "You currently have about {{available}} free space.",
"cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.", "cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
"completion_automatic:done": "Done:", "completion_automatic:done": "Done:",
"completion_automatic:failed": "Failed:", "completion_automatic:failed": "Failed:",
@ -20,7 +20,7 @@
"confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?",
"confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?", "confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?",
"confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?", "confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?",
"confirmlimiteddownload": "You are not currently connected to Wi-Fi. ", "confirmlimiteddownload": "You are not currently connected to Wi-Fi.",
"confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}.{{availableSpace}} Are you sure you want to continue?",
"couldnotloadsectioncontent": "Could not load the section content. Please try again later.", "couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
"couldnotloadsections": "Could not load the sections. Please try again later.", "couldnotloadsections": "Could not load the sections. Please try again later.",

@ -15,8 +15,8 @@
"errorreadingfile": "Error reading file.", "errorreadingfile": "Error reading file.",
"errorwhileuploading": "An error occurred during the file upload.", "errorwhileuploading": "An error occurred during the file upload.",
"file": "File", "file": "File",
"fileuploaded": "The file was successfully uploaded.",
"filesofthesetypes": "Accepted file types:", "filesofthesetypes": "Accepted file types:",
"fileuploaded": "The file was successfully uploaded.",
"invalidfiletype": "{{$a}} filetype cannot be accepted.", "invalidfiletype": "{{$a}} filetype cannot be accepted.",
"maxbytesfile": "The file {{$a.file}} is too large. The maximum size you can upload is {{$a.size}}.", "maxbytesfile": "The file {{$a.file}} is too large. The maximum size you can upload is {{$a.size}}.",
"microphonepermissiondenied": "Permission to access the microphone has been denied.", "microphonepermissiondenied": "Permission to access the microphone has been denied.",

@ -1,8 +1,8 @@
{ {
"a11yTitle:label": "Assistive Technologies label", "a11yTitle:label": "Assistive Technologies label",
"add": "Add",
"acceptTerms": "I accept the <a href=\":url\" target=\"_blank\">terms of use</a>", "acceptTerms": "I accept the <a href=\":url\" target=\"_blank\">terms of use</a>",
"accountDetailsLinkText": "here", "accountDetailsLinkText": "here",
"add": "Add",
"additionallicenseinfo": "Any additional information about the licence", "additionallicenseinfo": "Any additional information about the licence",
"address": "Address", "address": "Address",
"age": "Typical age", "age": "Typical age",
@ -15,11 +15,11 @@
"authorrole": "Author's role", "authorrole": "Author's role",
"back": "Back", "back": "Back",
"by": "by", "by": "by",
"cancellabel": "Cancel",
"cancelPublishConfirmationDialogCancelButtonText": "No", "cancelPublishConfirmationDialogCancelButtonText": "No",
"cancelPublishConfirmationDialogConfirmButtonText": "Yes", "cancelPublishConfirmationDialogConfirmButtonText": "Yes",
"cancelPublishConfirmationDialogDescription": "Are you sure you want to cancel the sharing process?", "cancelPublishConfirmationDialogDescription": "Are you sure you want to cancel the sharing process?",
"cancelPublishConfirmationDialogTitle": "Cancel sharing", "cancelPublishConfirmationDialogTitle": "Cancel sharing",
"cancellabel": "Cancel",
"ccattribution": "Attribution (CC BY)", "ccattribution": "Attribution (CC BY)",
"ccattributionnc": "Attribution-NonCommercial (CC BY-NC)", "ccattributionnc": "Attribution-NonCommercial (CC BY-NC)",
"ccattributionncnd": "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)", "ccattributionncnd": "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)",
@ -40,8 +40,8 @@
"connectionReestablished": "Connection reestablished.", "connectionReestablished": "Connection reestablished.",
"contactPerson": "Contact person", "contactPerson": "Contact person",
"contentCopied": "Content is copied to the clipboard", "contentCopied": "Content is copied to the clipboard",
"contentchanged": "This content has changed since you last used it.",
"contentLicenseTitle": "Content license info", "contentLicenseTitle": "Content license info",
"contentchanged": "This content has changed since you last used it.",
"contenttype": "Content type", "contenttype": "Content type",
"copyright": "Rights of use", "copyright": "Rights of use",
"copyrightinfo": "Copyright information", "copyrightinfo": "Copyright information",
@ -56,10 +56,10 @@
"discipline:dropdownButton": "Dropdown button", "discipline:dropdownButton": "Dropdown button",
"discipline:in": "in", "discipline:in": "in",
"discipline:searchPlaceholder": "Type to search for disciplines", "discipline:searchPlaceholder": "Type to search for disciplines",
"disciplines": "Disciplines",
"disciplineLabel": "Disciplines",
"disciplineDescription": "You can select multiple disciplines", "disciplineDescription": "You can select multiple disciplines",
"disciplineLabel": "Disciplines",
"disciplineLimitReachedMessage": "You can select up to :numDisciplines disciplines", "disciplineLimitReachedMessage": "You can select up to :numDisciplines disciplines",
"disciplines": "Disciplines",
"download": "Download", "download": "Download",
"downloadtitle": "Download this content as a H5P file.", "downloadtitle": "Download this content as a H5P file.",
"editInfoTitle": "Edit info for <strong>:title</strong>", "editInfoTitle": "Edit info for <strong>:title</strong>",
@ -78,9 +78,9 @@
"icon": "Icon", "icon": "Icon",
"iconDescription": "640x480px. If not selected content will use category icon", "iconDescription": "640x480px. If not selected content will use category icon",
"invalidAge": "Invalid input format for Typical age. Possible input formats separated by commas: \"1, 34-45, -50, -59-\".", "invalidAge": "Invalid input format for Typical age. Possible input formats separated by commas: \"1, 34-45, -50, -59-\".",
"keywordExists": "Keyword already exists!",
"keywords": "Keywords", "keywords": "Keywords",
"keywordsDescription": "You can add multiple keywords separated by commas. Press \"Enter\" or \"Add\" to confirm keywords", "keywordsDescription": "You can add multiple keywords separated by commas. Press \"Enter\" or \"Add\" to confirm keywords",
"keywordExists": "Keyword already exists!",
"keywordsExits": "Keywords already exists!", "keywordsExits": "Keywords already exists!",
"keywordsPlaceholder": "Add keywords", "keywordsPlaceholder": "Add keywords",
"language": "Language", "language": "Language",
@ -100,10 +100,10 @@
"licenseV1": "Version 1", "licenseV1": "Version 1",
"licenseV2": "Version 2", "licenseV2": "Version 2",
"licenseV3": "Version 3", "licenseV3": "Version 3",
"licenseVersionDescription": "Select a license version",
"licensee": "Licensee", "licensee": "Licensee",
"licenseextras": "Licence extras", "licenseextras": "Licence extras",
"licenseversion": "Licence version", "licenseversion": "Licence version",
"licenseVersionDescription": "Select a license version",
"logoUploadText": "Organization logo or avatar", "logoUploadText": "Organization logo or avatar",
"longDescription": "Long description", "longDescription": "Long description",
"longDescriptionPlaceholder": "Long description of your content", "longDescriptionPlaceholder": "Long description of your content",
@ -147,9 +147,9 @@
"screenshots": "Screenshots", "screenshots": "Screenshots",
"screenshotsDescription": "Add up to five screenshots of your content", "screenshotsDescription": "Add up to five screenshots of your content",
"share": "Share", "share": "Share",
"shared": "Shared",
"shareFailed": "Share failed.", "shareFailed": "Share failed.",
"shareTryAgain": "Something went wrong, please try to share again.", "shareTryAgain": "Something went wrong, please try to share again.",
"shared": "Shared",
"sharingNote": "All content details can be edited after sharing", "sharingNote": "All content details can be edited after sharing",
"shortDescription": "Short description", "shortDescription": "Short description",
"shortDescriptionPlaceholder": "Short description of your content", "shortDescriptionPlaceholder": "Short description of your content",

@ -19,4 +19,4 @@
"questionmessage": "Question {{$a}}: {{$b}}", "questionmessage": "Question {{$a}}: {{$b}}",
"questionno": "Question {{$a}}", "questionno": "Question {{$a}}",
"requiresgrading": "Requires grading" "requiresgrading": "Requires grading"
} }

@ -1,9 +1,9 @@
{ {
"filtersapplied": "There may be filters applied to this view. To edit filters or change the sorting order, <a href=\"{{$a}}\">open this report on your browser.</a>",
"hidecolumns": "Hide columns",
"modifiedby": "Modified by", "modifiedby": "Modified by",
"reports": "Reports", "reports": "Reports",
"filtersapplied": "There may be filters applied to this view. To edit filters or change the sorting order, <a href=\"{{$a}}\">open this report on your browser.</a>",
"reportsource": "Report source", "reportsource": "Report source",
"timecreated": "Time created",
"showcolumns": "Show columns", "showcolumns": "Show columns",
"hidecolumns": "Hide columns" "timecreated": "Time created"
} }

@ -8,14 +8,14 @@
"cannotsyncwithoutwifi": "Your device is not connected to Wi-Fi. Connect to a Wi-Fi network or turn off Data Saver in the app settings.", "cannotsyncwithoutwifi": "Your device is not connected to Wi-Fi. Connect to a Wi-Fi network or turn off Data Saver in the app settings.",
"changelanguage": "Change to {{$a}}", "changelanguage": "Change to {{$a}}",
"changelanguagealert": "Changing the language will restart the app.", "changelanguagealert": "Changing the language will restart the app.",
"colorscheme": "Color Scheme",
"colorscheme-dark": "Dark", "colorscheme-dark": "Dark",
"colorscheme-light": "Light", "colorscheme-light": "Light",
"colorscheme-system-notice": "System default mode will depend on your device support.",
"colorscheme-system": "System default", "colorscheme-system": "System default",
"colorscheme": "Color Scheme", "colorscheme-system-notice": "System default mode will depend on your device support.",
"compilationinfo": "Compilation info", "compilationinfo": "Compilation info",
"connectwifitosync": "Connect to a Wi-Fi network or turn off Data saver to synchronise sites.",
"connecttosync": "Your device is offline. Connect to the internet to synchronise sites.", "connecttosync": "Your device is offline. Connect to the internet to synchronise sites.",
"connectwifitosync": "Connect to a Wi-Fi network or turn off Data saver to synchronise sites.",
"copyinfo": "Copy device info on the clipboard", "copyinfo": "Copy device info on the clipboard",
"cordovadevicemodel": "Cordova device model", "cordovadevicemodel": "Cordova device model",
"cordovadeviceosversion": "Cordova device OS version", "cordovadeviceosversion": "Cordova device OS version",

@ -8,4 +8,4 @@
"replace": "Replace", "replace": "Replace",
"sharedfiles": "Shared files", "sharedfiles": "Shared files",
"successstorefile": "File successfully stored. Select the file to upload to your private files or use in an activity." "successstorefile": "File successfully stored. Select the file to upload to your private files or use in an activity."
} }

@ -3,16 +3,16 @@
"errorareanotsupported": "This tag area is not supported by the app.", "errorareanotsupported": "This tag area is not supported by the app.",
"inalltagcoll": "Everywhere", "inalltagcoll": "Everywhere",
"itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", "itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"",
"notagsfound": "No tags matching \"{{$a}}\" found",
"noresultsfor": "No results for \"{{$a}}\"", "noresultsfor": "No results for \"{{$a}}\"",
"notagsfound": "No tags matching \"{{$a}}\" found",
"searchtags": "Search tags", "searchtags": "Search tags",
"showingfirsttags": "Showing {{$a}} most popular tags", "showingfirsttags": "Showing {{$a}} most popular tags",
"tag": "Tag", "tag": "Tag",
"tagareabadgedescription": "There are {{count}} items.",
"tagarea_course": "Courses", "tagarea_course": "Courses",
"tagarea_course_modules": "Activities and resources", "tagarea_course_modules": "Activities and resources",
"tagarea_post": "Blog posts", "tagarea_post": "Blog posts",
"tagarea_user": "User interests", "tagarea_user": "User interests",
"tagareabadgedescription": "There are {{count}} items.",
"tags": "Tags", "tags": "Tags",
"warningareasnotsupported": "Some of the tag areas are not displayed because they are not supported by the app." "warningareasnotsupported": "Some of the tag areas are not displayed because they are not supported by the app."
} }