From 9ef2f709c75c082682d20b45e29c9a43ad8cb31e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 14 Apr 2015 09:42:34 +0200 Subject: [PATCH] i18n for default taiga names --- settings/common.py | 1 + taiga/base/api/fields.py | 17 +- taiga/base/api/serializers.py | 9 + taiga/base/fields.py | 32 ++ taiga/locale/en/LC_MESSAGES/django.po | 340 +++++++++++------- taiga/projects/history/serializers.py | 9 +- taiga/projects/serializers.py | 9 + taiga/projects/translations.py | 180 ++++++++++ .../test_users_resources.py | 1 + 9 files changed, 458 insertions(+), 140 deletions(-) create mode 100644 taiga/projects/translations.py diff --git a/settings/common.py b/settings/common.py index 3be896a0..2a219fe2 100644 --- a/settings/common.py +++ b/settings/common.py @@ -221,6 +221,7 @@ DEFAULT_FILE_STORAGE = "taiga.base.storage.FileSystemStorage" LOCALE_PATHS = ( os.path.join(BASE_DIR, "locale"), + os.path.join(BASE_DIR, "taiga", "locale"), ) SECRET_KEY = "aw3+t2r(8(0kkrhg8)gx6i96v5^kv%6cfep9wxfom0%7dy0m9e" diff --git a/taiga/base/api/fields.py b/taiga/base/api/fields.py index 48a2250c..942878f7 100644 --- a/taiga/base/api/fields.py +++ b/taiga/base/api/fields.py @@ -38,6 +38,7 @@ from django.utils.encoding import smart_text from django.utils.encoding import force_text from django.utils.encoding import is_protected_type from django.utils.functional import Promise +from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ from django.utils.datastructures import SortedDict @@ -133,6 +134,7 @@ def humanize_strptime(format_string): class Field(object): read_only = True + i18n = False creation_counter = 0 empty = "" type_name = None @@ -142,7 +144,7 @@ class Field(object): type_label = "field" widget = None - def __init__(self, source=None, label=None, help_text=None): + def __init__(self, source=None, label=None, help_text=None, i18n=False): self.parent = None self.creation_counter = Field.creation_counter @@ -159,6 +161,7 @@ class Field(object): self._errors = [] self._value = None self._name = None + self.i18n = i18n @property def errors(self): @@ -271,7 +274,7 @@ class WritableField(Field): def __init__(self, source=None, label=None, help_text=None, read_only=False, write_only=False, required=None, validators=[], error_messages=None, widget=None, - default=None, blank=None): + default=None, blank=None, i18n=False): # "blank" is to be deprecated in favor of "required" if blank is not None: @@ -280,7 +283,8 @@ class WritableField(Field): DeprecationWarning, stacklevel=2) required = not(blank) - super(WritableField, self).__init__(source=source, label=label, help_text=help_text) + super(WritableField, self).__init__(source=source, label=label, + help_text=help_text, i18n=i18n) self.read_only = read_only self.write_only = write_only @@ -486,6 +490,13 @@ class CharField(WritableField): return value return smart_text(value) + def to_native(self, value): + ret = super(CharField, self).to_native(value) + if self.i18n: + ret = ugettext(ret) + + return ret + class URLField(CharField): type_name = "URLField" diff --git a/taiga/base/api/serializers.py b/taiga/base/api/serializers.py index f82dcd2c..979fb520 100644 --- a/taiga/base/api/serializers.py +++ b/taiga/base/api/serializers.py @@ -673,6 +673,7 @@ class ModelSerializerOptions(SerializerOptions): def __init__(self, meta): super(ModelSerializerOptions, self).__init__(meta) self.model = getattr(meta, "model", None) + self.i18n_fields = getattr(meta, "i18n_fields", ()) self.read_only_fields = getattr(meta, "read_only_fields", ()) self.write_only_fields = getattr(meta, "write_only_fields", ()) @@ -837,6 +838,11 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer))) (field_name, self.__class__.__name__)) ret[field_name].write_only = True + # Add the `i18n` flag to any fields that have been specified + # in the `i18n_fields` option + for field_name in self.opts.i18n_fields: + ret[field_name].i18n = True + return ret def get_pk_field(self, model_field): @@ -929,6 +935,9 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer))) for attribute in attributes: kwargs.update({attribute: getattr(model_field, attribute)}) + if model_field.name in self.opts.i18n_fields: + kwargs["i18n"] = True + try: return self.field_mapping[model_field.__class__](**kwargs) except KeyError: diff --git a/taiga/base/fields.py b/taiga/base/fields.py index 8c49283f..69ce3868 100644 --- a/taiga/base/fields.py +++ b/taiga/base/fields.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from django.forms import widgets +from django.utils.translation import ugettext as _ from taiga.base.api import serializers @@ -36,6 +37,37 @@ class JsonField(serializers.WritableField): return data +class I18NJsonField(JsonField): + """ + Json objects serializer. + """ + widget = widgets.Textarea + + def __init__(self, i18n_fields=(), *args, **kwargs): + super(I18NJsonField, self).__init__(*args, **kwargs) + self.i18n_fields = i18n_fields + + def translate_values(self, d): + i18n_d = {} + for key, value in d.items(): + if isinstance(value, dict): + i18n_d[key] = self.translate_values(value) + + if key in self.i18n_fields: + if isinstance(value, list): + i18n_d[key] = [_(e) for e in value] + if isinstance(value, str): + i18n_d[key] = _(value) + else: + i18n_d[key] = value + + return i18n_d + + def to_native(self, obj): + i18n_obj = self.translate_values(obj) + return i18n_obj + + class PgArrayField(serializers.WritableField): """ PgArray objects serializer. diff --git a/taiga/locale/en/LC_MESSAGES/django.po b/taiga/locale/en/LC_MESSAGES/django.po index db271a69..dd309a6c 100644 --- a/taiga/locale/en/LC_MESSAGES/django.po +++ b/taiga/locale/en/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-04-14 16:26+0200\n" +"POT-Creation-Date: 2015-04-14 18:00+0200\n" "PO-Revision-Date: 2015-03-25 20:09+0100\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Taiga Dev Team \n" @@ -1618,160 +1618,236 @@ msgid "" "[Taiga] Added to the project '%(project)s'\n" msgstr "" -#: taiga/projects/translations.py:3 -msgid "?" -msgstr "" - -#: taiga/projects/translations.py:4 -msgid "0" -msgstr "" - -#: taiga/projects/translations.py:5 -msgid "1/2" -msgstr "" - -#: taiga/projects/translations.py:6 -msgid "1" -msgstr "" - -#: taiga/projects/translations.py:7 -msgid "2" -msgstr "" - -#: taiga/projects/translations.py:8 -msgid "3" -msgstr "" - -#: taiga/projects/translations.py:9 -msgid "5" -msgstr "" - -#: taiga/projects/translations.py:10 -msgid "8" -msgstr "" - -#: taiga/projects/translations.py:11 -msgid "10" -msgstr "" - -#: taiga/projects/translations.py:12 -msgid "15" -msgstr "" - -#: taiga/projects/translations.py:13 -msgid "20" -msgstr "" - -#: taiga/projects/translations.py:14 -msgid "40" -msgstr "" - -#: taiga/projects/translations.py:15 +#. Translators: Name of scrum project template. +#: taiga/projects/translations.py:28 msgid "Scrum" msgstr "" -#: taiga/projects/translations.py:16 -msgid "Archived" -msgstr "" - -#: taiga/projects/translations.py:17 -msgid "Back" -msgstr "" - -#: taiga/projects/translations.py:18 -msgid "Bug" -msgstr "" - -#: taiga/projects/translations.py:19 -msgid "Closed" -msgstr "" - -#: taiga/projects/translations.py:20 -msgid "Critical" -msgstr "" - -#: taiga/projects/translations.py:21 -msgid "Design" -msgstr "" - -#: taiga/projects/translations.py:22 -msgid "Done" -msgstr "" - -#: taiga/projects/translations.py:23 -msgid "Enhancement" -msgstr "" - -#: taiga/projects/translations.py:24 -msgid "Front" -msgstr "" - -#: taiga/projects/translations.py:25 -msgid "High" -msgstr "" - -#: taiga/projects/translations.py:26 -msgid "Important" -msgstr "" - -#: taiga/projects/translations.py:27 -msgid "In progress" -msgstr "" - -#: taiga/projects/translations.py:28 -msgid "Low" -msgstr "" - -#: taiga/projects/translations.py:29 -msgid "Minor" -msgstr "" - +#. Translators: Description of scrum project template. #: taiga/projects/translations.py:30 -msgid "Needs Info" +msgid "" +"The agile product backlog in Scrum is a prioritized features list, " +"containing short descriptions of all functionality desired in the product. " +"When applying Scrum, it's not necessary to start a project with a lengthy, " +"upfront effort to document all requirements. The Scrum product backlog is " +"then allowed to grow and change as more is learned about the product and its " +"customers" msgstr "" -#: taiga/projects/translations.py:31 +#. Translators: Name of kanban project template. +#: taiga/projects/translations.py:33 +msgid "Kanban" +msgstr "" + +#. Translators: Description of kanban project template. +#: taiga/projects/translations.py:35 +msgid "" +"Kanban is a method for managing knowledge work with an emphasis on just-in-" +"time delivery while not overloading the team members. In this approach, the " +"process, from definition of a task to its delivery to the customer, is " +"displayed for participants to see and team members pull work from a queue." +msgstr "" + +#. Translators: User story point value (value = undefined) +#: taiga/projects/translations.py:43 +msgid "?" +msgstr "" + +#. Translators: User story point value (value = 0) +#: taiga/projects/translations.py:45 +msgid "0" +msgstr "" + +#. Translators: User story point value (value = 0.5) +#: taiga/projects/translations.py:47 +msgid "1/2" +msgstr "" + +#. Translators: User story point value (value = 1) +#: taiga/projects/translations.py:49 +msgid "1" +msgstr "" + +#. Translators: User story point value (value = 2) +#: taiga/projects/translations.py:51 +msgid "2" +msgstr "" + +#. Translators: User story point value (value = 3) +#: taiga/projects/translations.py:53 +msgid "3" +msgstr "" + +#. Translators: User story point value (value = 5) +#: taiga/projects/translations.py:55 +msgid "5" +msgstr "" + +#. Translators: User story point value (value = 8) +#: taiga/projects/translations.py:57 +msgid "8" +msgstr "" + +#. Translators: User story point value (value = 10) +#: taiga/projects/translations.py:59 +msgid "10" +msgstr "" + +#. Translators: User story point value (value = 15) +#: taiga/projects/translations.py:61 +msgid "15" +msgstr "" + +#. Translators: User story point value (value = 20) +#: taiga/projects/translations.py:63 +msgid "20" +msgstr "" + +#. Translators: User story point value (value = 40) +#: taiga/projects/translations.py:65 +msgid "40" +msgstr "" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:73 taiga/projects/translations.py:96 +#: taiga/projects/translations.py:112 msgid "New" msgstr "" -#: taiga/projects/translations.py:32 -msgid "Normal" -msgstr "" - -#: taiga/projects/translations.py:33 -msgid "Postponed" -msgstr "" - -#: taiga/projects/translations.py:34 -msgid "Product Owner" -msgstr "" - -#: taiga/projects/translations.py:35 -msgid "Question" -msgstr "" - -#: taiga/projects/translations.py:36 +#. Translators: User story status +#: taiga/projects/translations.py:76 msgid "Ready" msgstr "" -#: taiga/projects/translations.py:37 +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:79 taiga/projects/translations.py:98 +#: taiga/projects/translations.py:114 +msgid "In progress" +msgstr "" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:82 taiga/projects/translations.py:100 +#: taiga/projects/translations.py:116 msgid "Ready for test" msgstr "" -#: taiga/projects/translations.py:38 +#. Translators: User story status +#: taiga/projects/translations.py:85 +msgid "Done" +msgstr "" + +#. Translators: User story status +#: taiga/projects/translations.py:88 +msgid "Archived" +msgstr "" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:102 taiga/projects/translations.py:118 +msgid "Closed" +msgstr "" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:104 taiga/projects/translations.py:120 +msgid "Needs Info" +msgstr "" + +#. Translators: Issue status +#: taiga/projects/translations.py:122 +msgid "Postponed" +msgstr "" + +#. Translators: Issue status +#: taiga/projects/translations.py:124 msgid "Rejected" msgstr "" -#: taiga/projects/translations.py:39 -msgid "Stakeholder" +#. Translators: Issue type +#: taiga/projects/translations.py:132 +msgid "Bug" msgstr "" -#: taiga/projects/translations.py:40 +#. Translators: Issue type +#: taiga/projects/translations.py:134 +msgid "Question" +msgstr "" + +#. Translators: Issue type +#: taiga/projects/translations.py:136 +msgid "Enhancement" +msgstr "" + +#. Translators: Issue priority +#: taiga/projects/translations.py:144 +msgid "Low" +msgstr "" + +#. Translators: Issue priority +#. Translators: Issue severity +#: taiga/projects/translations.py:146 taiga/projects/translations.py:159 +msgid "Normal" +msgstr "" + +#. Translators: Issue priority +#: taiga/projects/translations.py:148 +msgid "High" +msgstr "" + +#. Translators: Issue severity +#: taiga/projects/translations.py:155 +msgid "Wishlist" +msgstr "" + +#. Translators: Issue severity +#: taiga/projects/translations.py:157 +msgid "Minor" +msgstr "" + +#. Translators: Issue severity +#: taiga/projects/translations.py:161 +msgid "Important" +msgstr "" + +#. Translators: Issue severity +#: taiga/projects/translations.py:163 +msgid "Critical" +msgstr "" + +#. Translators: User role +#: taiga/projects/translations.py:170 msgid "UX" msgstr "" -#: taiga/projects/translations.py:41 -msgid "Wishlist" +#. Translators: User role +#: taiga/projects/translations.py:172 +msgid "Design" +msgstr "" + +#. Translators: User role +#: taiga/projects/translations.py:174 +msgid "Front" +msgstr "" + +#. Translators: User role +#: taiga/projects/translations.py:176 +msgid "Back" +msgstr "" + +#. Translators: User role +#: taiga/projects/translations.py:178 +msgid "Product Owner" +msgstr "" + +#. Translators: User role +#: taiga/projects/translations.py:180 +msgid "Stakeholder" msgstr "" #: taiga/projects/userstories/api.py:170 diff --git a/taiga/projects/history/serializers.py b/taiga/projects/history/serializers.py index 6e0b0911..4834e504 100644 --- a/taiga/projects/history/serializers.py +++ b/taiga/projects/history/serializers.py @@ -15,20 +15,19 @@ # along with this program. If not, see . from taiga.base.api import serializers - -from taiga.base.fields import JsonField +from taiga.base.fields import JsonField, I18NJsonField from . import models +HISTORY_ENTRY_I18N_FIELDS=("points", "status", "severity", "priority", "type") class HistoryEntrySerializer(serializers.ModelSerializer): diff = JsonField() snapshot = JsonField() - values = JsonField() - values_diff = JsonField() + values = I18NJsonField(i18n_fields=HISTORY_ENTRY_I18N_FIELDS) + values_diff = I18NJsonField(i18n_fields=HISTORY_ENTRY_I18N_FIELDS) user = JsonField() delete_comment_user = JsonField() class Meta: model = models.HistoryEntry - diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index f4d51b0a..28f9cad5 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -47,6 +47,7 @@ from .custom_attributes.serializers import IssueCustomAttributeSerializer class PointsSerializer(serializers.ModelSerializer): class Meta: model = models.Points + i18n_fields = ("name",) def validate_name(self, attrs, source): """ @@ -67,8 +68,10 @@ class PointsSerializer(serializers.ModelSerializer): class UserStoryStatusSerializer(serializers.ModelSerializer): + class Meta: model = models.UserStoryStatus + i18n_fields = ("name",) def validate_name(self, attrs, source): """ @@ -93,6 +96,7 @@ class UserStoryStatusSerializer(serializers.ModelSerializer): class TaskStatusSerializer(serializers.ModelSerializer): class Meta: model = models.TaskStatus + i18n_fields = ("name",) def validate_name(self, attrs, source): """ @@ -115,16 +119,19 @@ class TaskStatusSerializer(serializers.ModelSerializer): class SeveritySerializer(serializers.ModelSerializer): class Meta: model = models.Severity + i18n_fields = ("name",) class PrioritySerializer(serializers.ModelSerializer): class Meta: model = models.Priority + i18n_fields = ("name",) class IssueStatusSerializer(serializers.ModelSerializer): class Meta: model = models.IssueStatus + i18n_fields = ("name",) def validate_name(self, attrs, source): """ @@ -147,6 +154,7 @@ class IssueStatusSerializer(serializers.ModelSerializer): class IssueTypeSerializer(serializers.ModelSerializer): class Meta: model = models.IssueType + i18n_fields = ("name",) ###################################################### @@ -380,3 +388,4 @@ class ProjectTemplateSerializer(serializers.ModelSerializer): class Meta: model = models.ProjectTemplate read_only_fields = ("created_date", "modified_date") + i18n_fields = ("name", "description") diff --git a/taiga/projects/translations.py b/taiga/projects/translations.py new file mode 100644 index 00000000..347eb6c8 --- /dev/null +++ b/taiga/projects/translations.py @@ -0,0 +1,180 @@ +# Copyright (C) 2015 Andrey Antukh +# Copyright (C) 2015 Jesús Espino +# Copyright (C) 2015 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# NOTE: This file is useful to translate default projects templates. Remember update +# when taiga/projects/fixtures/initial_project_templates.json change. + +from django.utils.translation import ugettext as _ + + +########################## +## Default template information +########################## + +# Translators: Name of scrum project template. +_("Scrum") +# Translators: Description of scrum project template. +_("The agile product backlog in Scrum is a prioritized features list, containing short descriptions of all functionality desired in the product. When applying Scrum, it's not necessary to start a project with a lengthy, upfront effort to document all requirements. The Scrum product backlog is then allowed to grow and change as more is learned about the product and its customers") + +# Translators: Name of kanban project template. +_("Kanban") +# Translators: Description of kanban project template. +_("Kanban is a method for managing knowledge work with an emphasis on just-in-time delivery while not overloading the team members. In this approach, the process, from definition of a task to its delivery to the customer, is displayed for participants to see and team members pull work from a queue.") + + +########################## +## US Points +########################## + +# Translators: User story point value (value = undefined) +_("?") +# Translators: User story point value (value = 0) +_("0") +# Translators: User story point value (value = 0.5) +_("1/2") +# Translators: User story point value (value = 1) +_("1") +# Translators: User story point value (value = 2) +_("2") +# Translators: User story point value (value = 3) +_("3") +# Translators: User story point value (value = 5) +_("5") +# Translators: User story point value (value = 8) +_("8") +# Translators: User story point value (value = 10) +_("10") +# Translators: User story point value (value = 15) +_("15") +# Translators: User story point value (value = 20) +_("20") +# Translators: User story point value (value = 40) +_("40") + + +########################## +## US Statuses +########################## + +# Translators: User story status +_("New") + +# Translators: User story status +_("Ready") + +# Translators: User story status +_("In progress") + +# Translators: User story status +_("Ready for test") + +# Translators: User story status +_("Done") + +# Translators: User story status +_("Archived") + + +########################## +## Task Statuses +########################## + +# Translators: Task status +_("New") +# Translators: Task status +_("In progress") +# Translators: Task status +_("Ready for test") +# Translators: Task status +_("Closed") +# Translators: Task status +_("Needs Info") + + +########################## +## Issue Statuses +########################## + +# Translators: Issue status +_("New") +# Translators: Issue status +_("In progress") +# Translators: Issue status +_("Ready for test") +# Translators: Issue status +_("Closed") +# Translators: Issue status +_("Needs Info") +# Translators: Issue status +_("Postponed") +# Translators: Issue status +_("Rejected") + + +########################## +## Issue Statuses +########################## + +# Translators: Issue type +_("Bug") +# Translators: Issue type +_("Question") +# Translators: Issue type +_("Enhancement") + + +########################## +## Priorities +########################## + +# Translators: Issue priority +_("Low") +# Translators: Issue priority +_("Normal") +# Translators: Issue priority +_("High") + + +########################## +## Severities +########################## +# Translators: Issue severity +_("Wishlist") +# Translators: Issue severity +_("Minor") +# Translators: Issue severity +_("Normal") +# Translators: Issue severity +_("Important") +# Translators: Issue severity +_("Critical") + + +########################## +## Roles +########################## +# Translators: User role +_("UX") +# Translators: User role +_("Design") +# Translators: User role +_("Front") +# Translators: User role +_("Back") +# Translators: User role +_("Product Owner") +# Translators: User role +_("Stakeholder") diff --git a/tests/integration/resources_permissions/test_users_resources.py b/tests/integration/resources_permissions/test_users_resources.py index 7e3f9659..6c1270ad 100644 --- a/tests/integration/resources_permissions/test_users_resources.py +++ b/tests/integration/resources_permissions/test_users_resources.py @@ -72,6 +72,7 @@ def test_user_update(client, data): user_data = UserSerializer(data.registered_user).data user_data["full_name"] = "test" user_data = json.dumps(user_data) + results = helper_test_http_method(client, 'put', url, user_data, users) assert results == [401, 200, 403, 200]