diff --git a/CHANGELOG b/CHANGELOG index 3808f39b5..0e9df1dc2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ Other: - Changed api for plugins. Used entry points to detect them automaticly. - Renamed config api classes. - Renamed some classes of the poll api. +- Inserted command line option to translate config strings during database setup. - Inserted api for the personal info widget. - Changed api for main menu entries. - Enhanced http error pages. diff --git a/openslides/__main__.py b/openslides/__main__.py index 11c5cb9c8..bbf39cd15 100644 --- a/openslides/__main__.py +++ b/openslides/__main__.py @@ -21,6 +21,7 @@ from openslides.utils.main import ( get_port, setup_django_settings_module, start_browser, + translate_customizable_strings, write_settings) @@ -87,7 +88,7 @@ def parse_args(): 'start', help='Setup settings and database, start tornado webserver, launch the ' 'default web browser and open the webinterface.') - add_general_arguments(subcommand_start, ('settings', 'user_data_path', 'address', 'port')) + add_general_arguments(subcommand_start, ('settings', 'user_data_path', 'language', 'address', 'port')) subcommand_start.add_argument( '--no-browser', action='store_true', @@ -113,7 +114,7 @@ def parse_args(): subcommand_syncdb = subparsers.add_parser( 'syncdb', help='Create or update database tables.') - add_general_arguments(subcommand_syncdb, ('settings', 'user_data_path')) + add_general_arguments(subcommand_syncdb, ('settings', 'user_data_path', 'language')) subcommand_syncdb.set_defaults(callback=syncdb) # Subcommand createsuperuser @@ -191,6 +192,11 @@ def add_general_arguments(subcommand, arguments): 'when a new settings file is created. The given path is only ' 'written into the new settings file. Default according to the ' 'OpenSlides is at the moment %s' % get_default_user_data_path(openslides_type))) + general_arguments['language'] = ( + ('-l', '--language'), + dict(help='Language code. All customizable strings will be translated ' + 'during database setup. See https://www.transifex.com/projects/p/openslides/ ' + 'for supported languages.')) general_arguments['address'] = ( ('-a', '--address',), dict(default='0.0.0.0', help='IP address to listen on. Default is %(default)s.')) @@ -250,6 +256,8 @@ def syncdb(settings, args): print('Clearing old search index...') execute_from_command_line(["", "clear_index", "--noinput"]) execute_from_command_line(["", "syncdb", "--noinput"]) + if args.language: + translate_customizable_strings(args.language) return 0 diff --git a/openslides/assignment/signals.py b/openslides/assignment/signals.py index 942bbe74e..d169cc66e 100644 --- a/openslides/assignment/signals.py +++ b/openslides/assignment/signals.py @@ -43,6 +43,7 @@ def setup_assignment_config(sender, **kwargs): assignment_pdf_title = ConfigVariable( name='assignment_pdf_title', default_value=_('Elections'), + translatable=True, form_field=forms.CharField( widget=forms.TextInput(), required=False, diff --git a/openslides/config/api.py b/openslides/config/api.py index 43bffe940..58a4b4c7f 100644 --- a/openslides/config/api.py +++ b/openslides/config/api.py @@ -53,8 +53,8 @@ class ConfigHandler(object): def setup_cache(self): """ - Loads all config variables from the database and by sending a - signal to get the default into the cache. + Loads all config variables from the database by sending a signal to + save the default to the cache. """ self._cache = {} for receiver, config_collection in config_signal.send(sender='setup_cache'): @@ -73,6 +73,15 @@ class ConfigHandler(object): else: return True + def get_all_translatable(self): + """ + Generator to get all config variables as strings when their values are + intended to be translated. + """ + for receiver, config_collection in config_signal.send(sender='get_all_translatable'): + for config_variable in config_collection.variables: + if config_variable.translatable: + yield config_variable.name config = ConfigHandler() """ @@ -162,10 +171,13 @@ class ConfigVariable(object): arguments 'name' and 'default_value' are required. The keyword argument 'form_field' has to be set if the variable should appear on the ConfigView. The argument 'on_change' can get a callback - which is called every time, the variable is changed. + which is called every time, the variable is changed. If the argument + 'translatable' is set, OpenSlides is able to translate the value during + setup of the database if the admin uses the respective command line option. """ - def __init__(self, name, default_value, form_field=None, on_change=None): + def __init__(self, name, default_value, form_field=None, on_change=None, translatable=False): self.name = name self.default_value = default_value self.form_field = form_field self.on_change = on_change + self.translatable = translatable diff --git a/openslides/core/signals.py b/openslides/core/signals.py index d44afac12..4e84d46cf 100644 --- a/openslides/core/signals.py +++ b/openslides/core/signals.py @@ -29,6 +29,7 @@ def setup_general_config(sender, **kwargs): event_description = ConfigVariable( name='event_description', default_value=_('Presentation and assembly system'), + translatable=True, form_field=forms.CharField( widget=forms.TextInput(), label=ugettext_lazy('Short description of event'), @@ -104,6 +105,7 @@ def setup_general_config(sender, **kwargs): welcome_title = ConfigVariable( name='welcome_title', default_value=_('Welcome to OpenSlides'), + translatable=True, form_field=forms.CharField( widget=forms.TextInput(), label=ugettext_lazy('Title'), @@ -113,6 +115,7 @@ def setup_general_config(sender, **kwargs): welcome_text = ConfigVariable( name='welcome_text', default_value=_('[Place for your welcome text.]'), + translatable=True, form_field=forms.CharField( widget=forms.Textarea(), label=ugettext_lazy('Welcome text'), diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index b581d6530..9fc8f22d2 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -40,6 +40,7 @@ def setup_motion_config(sender, **kwargs): motion_preamble = ConfigVariable( name='motion_preamble', default_value=_('The assembly may decide,'), + translatable=True, form_field=forms.CharField( widget=forms.TextInput(), required=False, @@ -66,6 +67,7 @@ def setup_motion_config(sender, **kwargs): motion_pdf_title = ConfigVariable( name='motion_pdf_title', default_value=_('Motions'), + translatable=True, form_field=forms.CharField( widget=forms.TextInput(), required=False, diff --git a/openslides/participant/signals.py b/openslides/participant/signals.py index 7664595b4..139950d93 100644 --- a/openslides/participant/signals.py +++ b/openslides/participant/signals.py @@ -23,6 +23,7 @@ def setup_participant_config(sender, **kwargs): participant_pdf_welcometitle = ConfigVariable( name='participant_pdf_welcometitle', default_value=_('Welcome to OpenSlides!'), + translatable=True, form_field=forms.CharField( widget=forms.Textarea(), required=False, @@ -31,6 +32,7 @@ def setup_participant_config(sender, **kwargs): participant_pdf_welcometext = ConfigVariable( name='participant_pdf_welcometext', default_value=_('[Place for your welcome and help text.]'), + translatable=True, form_field=forms.CharField( widget=forms.Textarea(), required=False, diff --git a/openslides/utils/main.py b/openslides/utils/main.py index 5485c5e0c..896e966a3 100644 --- a/openslides/utils/main.py +++ b/openslides/utils/main.py @@ -12,6 +12,8 @@ import webbrowser from base64 import b64encode from django.core.exceptions import ImproperlyConfigured from django.conf import ENVIRONMENT_VARIABLE +from django.utils.translation import activate, check_for_language, get_language +from django.utils.translation import ugettext as _ UNIX_VERSION = 'Unix Version' WINDOWS_VERSION = 'Windows Version' @@ -301,3 +303,16 @@ def get_database_path_from_settings(): if default.get('ENGINE') != 'django.db.backends.sqlite3': database_path = None return database_path + + +def translate_customizable_strings(language_code): + """ + Translates all translatable config values and saves them into database. + """ + if check_for_language(language_code): + from openslides.config.api import config + current_language = get_language() + activate(language_code) + for name in config.get_all_translatable(): + config[name] = _(config[name]) + activate(current_language) diff --git a/tests/utils/test_main.py b/tests/utils/test_main.py index d1d8d11c3..20506564a 100644 --- a/tests/utils/test_main.py +++ b/tests/utils/test_main.py @@ -12,6 +12,7 @@ from openslides.__main__ import ( runserver, start, syncdb) +from openslides.config.api import config from openslides.utils.main import ( get_browser_url, get_database_path_from_settings, @@ -22,6 +23,7 @@ from openslides.utils.main import ( PortIsBlockedError, setup_django_settings_module, start_browser, + translate_customizable_strings, UNIX_VERSION, WINDOWS_PORTABLE_VERSION) from openslides.utils.test import TestCase @@ -91,6 +93,11 @@ class TestFunctions(TestCase): def test_get_database_path_from_settings_memory(self): self.assertEqual(get_database_path_from_settings(), ':memory:') + def test_translate_customizable_strings(self): + self.assertEqual(config['event_description'], 'Presentation and assembly system') + translate_customizable_strings('de') + self.assertEqual(config['event_description'], u'Präsentations- und Versammlungssystem') + class TestOtherFunctions(TestCase): """ @@ -127,6 +134,7 @@ class TestOtherFunctions(TestCase): def test_syncdb(self, mock_execute_from_command_line, mock_os, mock_exists): mock_exists.return_value = True mock_args = MagicMock() + mock_args.language = None syncdb(settings=None, args=mock_args) self.assertTrue(mock_execute_from_command_line.called)