Custom Translations
This commit is contained in:
parent
97a1431c32
commit
b30919eada
@ -73,6 +73,7 @@ Core:
|
||||
- Added config for disabling header and footer in the projector [#3357].
|
||||
- Updated CKEditor to 4.7 [#3375].
|
||||
- Reduced ckeditor toolbar for inline editing [#3368].
|
||||
- Added custom translations in config [#3383].
|
||||
|
||||
Mediafiles:
|
||||
- Fixed reloading of PDF on page change [#3274].
|
||||
|
@ -29,6 +29,7 @@ INPUT_TYPE_MAPPING = {
|
||||
'datetimepicker': int,
|
||||
'majorityMethod': str,
|
||||
'logo': dict,
|
||||
'translations': list,
|
||||
}
|
||||
|
||||
|
||||
@ -134,6 +135,22 @@ class ConfigHandler:
|
||||
if not isinstance(value[required_entry], str):
|
||||
raise ConfigError(_('{} has to be a string.'.format(required_entry)))
|
||||
|
||||
if config_variable.input_type == 'translations':
|
||||
if not isinstance(value, list):
|
||||
raise ConfigError(_('Translations has to be a list.'))
|
||||
for entry in value:
|
||||
if not isinstance(entry, dict):
|
||||
raise ConfigError(_('Every value has to be a dict, not {}.'.format(type(entry))))
|
||||
whitelist = (
|
||||
'original',
|
||||
'translation',
|
||||
)
|
||||
for required_entry in whitelist:
|
||||
if required_entry not in entry:
|
||||
raise ConfigError(_('{} has to be given.'.format(required_entry)))
|
||||
if not isinstance(entry[required_entry], str):
|
||||
raise ConfigError(_('{} has to be a string.'.format(required_entry)))
|
||||
|
||||
# Save the new value to the database.
|
||||
db_value = ConfigStore.objects.get(key=key)
|
||||
db_value.value = value
|
||||
|
@ -322,3 +322,12 @@ def get_config_variables():
|
||||
weight=312,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
|
||||
# Custom translations
|
||||
yield ConfigVariable(
|
||||
name='translations',
|
||||
label='Custom translations',
|
||||
default_value=[],
|
||||
input_type='translations',
|
||||
weight=1000,
|
||||
group='Custom translations')
|
||||
|
@ -1095,11 +1095,24 @@ img {
|
||||
|
||||
/** Config **/
|
||||
.input-comments > div {
|
||||
margin-bottom: 5px
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.config-checkbox {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.config-translations > div {
|
||||
margin-bottom: 5px;
|
||||
padding-right: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.config-translations .inputs input {
|
||||
width: 47%;
|
||||
}
|
||||
.config-translations .inputs .arrow {
|
||||
width: 6%;
|
||||
float: left;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/** Pojector sidebar **/
|
||||
.col2 .projectorSelector {
|
||||
|
@ -254,6 +254,52 @@ angular.module('OpenSlidesApp.core', [
|
||||
}
|
||||
])
|
||||
|
||||
// Hook into gettextCatalog to include custom translations by wrapping
|
||||
// the getString method. The translations are stored in the config.
|
||||
.decorator('gettextCatalog', [
|
||||
'$delegate',
|
||||
'$rootScope',
|
||||
function ($delegate, $rootScope) {
|
||||
var oldGetString = $delegate.getString;
|
||||
var customTranslations = {};
|
||||
|
||||
$delegate.getString = function () {
|
||||
var translated = oldGetString.apply($delegate, arguments);
|
||||
if (customTranslations[translated]) {
|
||||
translated = customTranslations[translated];
|
||||
}
|
||||
return translated;
|
||||
};
|
||||
$delegate.setCustomTranslations = function (translations) {
|
||||
customTranslations = translations;
|
||||
$rootScope.$broadcast('gettextLanguageChanged');
|
||||
};
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
])
|
||||
|
||||
.run([
|
||||
'$rootScope',
|
||||
'Config',
|
||||
'gettextCatalog',
|
||||
function ($rootScope, Config, gettextCatalog) {
|
||||
$rootScope.$watch(function () {
|
||||
return Config.lastModified('translations');
|
||||
}, function () {
|
||||
var translations = Config.get('translations');
|
||||
if (translations) {
|
||||
var customTranslations = {};
|
||||
_.forEach(translations.value, function (entry) {
|
||||
customTranslations[entry.original] = entry.translation;
|
||||
});
|
||||
// Update all translate directives
|
||||
gettextCatalog.setCustomTranslations(customTranslations);
|
||||
}
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
// set browser language as default language for OpenSlides
|
||||
.run([
|
||||
'gettextCatalog',
|
||||
|
@ -825,6 +825,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
colorpicker: 'colorpicker',
|
||||
datetimepicker: 'datetimepicker',
|
||||
majorityMethod: 'choice',
|
||||
translations: 'translations',
|
||||
}[type];
|
||||
};
|
||||
|
||||
@ -1134,6 +1135,19 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
$scope.save(configOption, parent.value);
|
||||
};
|
||||
|
||||
// For custom translations input
|
||||
$scope.addTranslation = function (configOption, parent) {
|
||||
parent.value.push({
|
||||
original: gettextCatalog.getString('New'),
|
||||
translation: gettextCatalog.getString('New'),
|
||||
});
|
||||
$scope.save(configOption, parent.value);
|
||||
};
|
||||
$scope.removeTranslation = function (configOption, parent, index) {
|
||||
parent.value.splice(index, 1);
|
||||
$scope.save(configOption, parent.value);
|
||||
};
|
||||
|
||||
// For majority method
|
||||
angular.forEach(
|
||||
_.filter($scope.configGroups, function (configGroup) {
|
||||
@ -1912,6 +1926,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
gettext('PDF footer logo');
|
||||
gettext('Web interface header logo');
|
||||
gettext('PDF ballot paper logo');
|
||||
gettext('Custom translations');
|
||||
|
||||
// Mark the string 'Default projector' here, because it does not appear in the templates.
|
||||
gettext('Default projector');
|
||||
|
@ -95,6 +95,41 @@
|
||||
ng-options="option.value as option.display_name | translate for option in choices">
|
||||
</select>
|
||||
|
||||
<!-- custom trnaslations -->
|
||||
<div class="config-translations" ng-if="type === 'translations'">
|
||||
<div ng-repeat="entry in $parent.value" class="input-group">
|
||||
<div class="inputs">
|
||||
<input ng-model="entry.original"
|
||||
ng-model-options="{debounce: 1000}"
|
||||
ng-change="save(configOption, $parent.value)"
|
||||
class="form-control"
|
||||
id="{{ key }}_original"
|
||||
type="text">
|
||||
<span class="arrow form-control"><i class="fa fa-arrow-right"></i></span>
|
||||
<input ng-model="entry.translation"
|
||||
ng-model-options="{debounce: 1000}"
|
||||
ng-change="save(configOption, $parent.value)"
|
||||
class="form-control"
|
||||
id="{{ key }}_translated"
|
||||
type="text">
|
||||
</div>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-click="removeTranslation(configOption, $parent, $index)">
|
||||
<i class="fa fa-minus"></i>
|
||||
<translate>Remove</translate>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" ng-click="addTranslation(configOption, $parent)"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-plus"></i>
|
||||
<translate>Add new custom translation</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="success-{{ key }}" class="input-group-addon" ng-if="configOption.success !== undefined">
|
||||
<i class="fa fa-lg fa-check-circle text-success"
|
||||
ng-if="configOption.success === true"></i>
|
||||
|
Loading…
Reference in New Issue
Block a user