Merge pull request #3383 from FinnStutzenstein/CustomTranslations

Custom Translations
This commit is contained in:
Norman Jäckel 2017-09-05 22:15:21 +02:00 committed by GitHub
commit c9ad15621c
7 changed files with 137 additions and 1 deletions

View File

@ -74,6 +74,7 @@ Core:
- Added config for disabling header and footer in the projector [#3357]. - Added config for disabling header and footer in the projector [#3357].
- Updated CKEditor to 4.7 [#3375]. - Updated CKEditor to 4.7 [#3375].
- Reduced ckeditor toolbar for inline editing [#3368]. - Reduced ckeditor toolbar for inline editing [#3368].
- Added custom translations in config [#3383].
Mediafiles: Mediafiles:
- Fixed reloading of PDF on page change [#3274]. - Fixed reloading of PDF on page change [#3274].

View File

@ -20,6 +20,7 @@ INPUT_TYPE_MAPPING = {
'datetimepicker': int, 'datetimepicker': int,
'majorityMethod': str, 'majorityMethod': str,
'logo': dict, 'logo': dict,
'translations': list,
} }
@ -125,6 +126,22 @@ class ConfigHandler:
if not isinstance(value[required_entry], str): if not isinstance(value[required_entry], str):
raise ConfigError(_('{} has to be a string.'.format(required_entry))) 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. # Save the new value to the database.
db_value = ConfigStore.objects.get(key=key) db_value = ConfigStore.objects.get(key=key)
db_value.value = value db_value.value = value

View File

@ -322,3 +322,12 @@ def get_config_variables():
weight=312, weight=312,
group='Logo', group='Logo',
hidden=True) hidden=True)
# Custom translations
yield ConfigVariable(
name='translations',
label='Custom translations',
default_value=[],
input_type='translations',
weight=1000,
group='Custom translations')

View File

@ -1120,11 +1120,24 @@ img {
/** Config **/ /** Config **/
.input-comments > div { .input-comments > div {
margin-bottom: 5px margin-bottom: 5px;
} }
.config-checkbox { .config-checkbox {
padding: 6px 12px; 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 **/ /** Pojector sidebar **/
.col2 .projectorSelector { .col2 .projectorSelector {

View File

@ -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 // set browser language as default language for OpenSlides
.run([ .run([
'gettextCatalog', 'gettextCatalog',

View File

@ -825,6 +825,7 @@ angular.module('OpenSlidesApp.core.site', [
colorpicker: 'colorpicker', colorpicker: 'colorpicker',
datetimepicker: 'datetimepicker', datetimepicker: 'datetimepicker',
majorityMethod: 'choice', majorityMethod: 'choice',
translations: 'translations',
}[type]; }[type];
}; };
@ -1134,6 +1135,19 @@ angular.module('OpenSlidesApp.core.site', [
$scope.save(configOption, parent.value); $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 // For majority method
angular.forEach( angular.forEach(
_.filter($scope.configGroups, function (configGroup) { _.filter($scope.configGroups, function (configGroup) {
@ -1912,6 +1926,7 @@ angular.module('OpenSlidesApp.core.site', [
gettext('PDF footer logo'); gettext('PDF footer logo');
gettext('Web interface header logo'); gettext('Web interface header logo');
gettext('PDF ballot paper logo'); gettext('PDF ballot paper logo');
gettext('Custom translations');
// Mark the string 'Default projector' here, because it does not appear in the templates. // Mark the string 'Default projector' here, because it does not appear in the templates.
gettext('Default projector'); gettext('Default projector');

View File

@ -95,6 +95,41 @@
ng-options="option.value as option.display_name | translate for option in choices"> ng-options="option.value as option.display_name | translate for option in choices">
</select> </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"> <span id="success-{{ key }}" class="input-group-addon" ng-if="configOption.success !== undefined">
<i class="fa fa-lg fa-check-circle text-success" <i class="fa fa-lg fa-check-circle text-success"
ng-if="configOption.success === true"></i> ng-if="configOption.success === true"></i>