diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8d19631..3d0ecac 100755 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,15 +2,19 @@ Django Lingua Changelog ======================== -Version 0.1, 22nd February 2010 +Version 0.9.0, 11th February 2016 ======================== -Initial Release +Version 0.9.0 + +Version 0.8.1, 13th November 2010 +======================== +Version 0.8.1 Version 0.8.0, 12th November 2010 ======================== Version 0.8.0 -Version 0.8.1, 13th November 2010 +Version 0.1, 22nd February 2010 ======================== -Version 0.8.1 +Initial Release diff --git a/HISTORY.txt b/HISTORY.txt index 8e46b05..7330a81 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,3 +1,12 @@ +Version 0.9.0 +------------- +- adapt code for supporting Django >= 1.9 +- Use py file instead of an html file with trans. +- Place created translations python file in each related app. Better maintainability. +- Inherits from an abstract Translation Model +- Rework admin +- Add LINGUA_DEFAULT settings (set the msgid key language, default to 'en'). + Version 0.8.0 -------------- - access direct the field translation i.e. _ name_de or name_es diff --git a/HOWTO.txt b/HOWTO.txt index c6ba589..886f3f6 100755 --- a/HOWTO.txt +++ b/HOWTO.txt @@ -15,9 +15,9 @@ from django.db import models from lingua import translation - class Table(models.Model): - class Translation(translation.Translation): - name = models.CharField(max_length=32) + class Table(translation.TranslationModel): + name = models.CharField(max_length=32) + _translation_fields = ('name',) 5. Active the admin(optional) from lingua.admin import LinguaModelAdmin @@ -30,11 +30,15 @@ admin.site.register( SomeModel, SomeAdminClass ) + You can set `LINGUA_DEFAULT` to a specified language in you settings (default to 'en'). + In the admin, it will display only your msgid and languages msgstr except for 'en' language + which is in most case the msgid language. + 6. Fill some data into the database to translate 7. Run ./manage.py collectmessages - It will fetch all data to translate and store them into the file 'db_translation.html'. + It will fetch all data to translate and store them into the file 'extra_translations.py' for each app. 8. Run django-admin.py makemessages -l @@ -44,7 +48,7 @@ 10. Run django-admin.py compilemessages 11. In the view you can use like _, i.e. name_de or name_es. - Use _00 to get the original value + Use _00 to get the original value. Done! diff --git a/LICENSE.txt b/LICENSE.txt index 6d77a93..d19d33c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,3 +1,6 @@ +Copyright (c) Guillaume Cisco + + Copyright (c) aquarianhouse.com | Georg Kasmin. All rights reserved. diff --git a/README.txt b/README.txt index b446a0d..76a67a3 100755 --- a/README.txt +++ b/README.txt @@ -9,4 +9,10 @@ Reduce the database hits to 0 * Translate it directly in the admin interface * Access the translation to each field to each language -Works >= 1.0 +Compatible Django >= 1.9 +Should work for previous version of Django, not tested yet. + + +TODO: +Find a way to mixin the model without using a mixin model. +Maybe use `contribute_class` as used before. diff --git a/lingua/__init__.py b/lingua/__init__.py index 27ab88f..dd15221 100755 --- a/lingua/__init__.py +++ b/lingua/__init__.py @@ -1,5 +1,6 @@ -VERSION = (0, 8, 1) +VERSION = (0, 9, 0) __version__ = ".".join(map(str, VERSION)) + def get_version(): return __version__ \ No newline at end of file diff --git a/lingua/admin.py b/lingua/admin.py index 267a9bc..aaefcb2 100755 --- a/lingua/admin.py +++ b/lingua/admin.py @@ -1,38 +1,8 @@ -""" -File: admin.py - -Copyright (c) aquarianhouse.com | Georg Kasmin. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" from django.contrib import admin -from django import forms -import os, sys +import os import polib +from django.apps import apps + class LinguaModelAdmin(admin.ModelAdmin): @@ -47,11 +17,12 @@ def get_fields_with_type(model, fields_type): return fields @staticmethod - def get_fields(model): - return ['_'.join((field, l)) for field in model._translation_fields for l in model._languages.keys()] + def get_admin_fields(request, obj=None): + return ['_'.join((field, l)) for field in obj._translation_fields for l in obj._languages.keys()] def get_form(self, request, obj=None, **kwargs): + kwargs['fields'] = None form = super(LinguaModelAdmin, self).get_form(request, obj, **kwargs) class LinguaAdminForm(form): @@ -80,14 +51,11 @@ def save(self, *args, **kwargs): from django.conf import settings from lingua.utils import clear_gettext_cache - #TODO make pretty - if settings.SETTINGS_MODULE is not None: - parts = settings.SETTINGS_MODULE.split('.') - project = __import__(parts[0]) - self.projectpath = os.path.join(os.path.dirname(project.__file__), 'locale') + if model._meta.app_label: + self.projectpath = os.path.join(apps.app_configs[model._meta.app_label].path, 'locale') self.languages_po = {} for l in dict(settings.LANGUAGES).keys(): - p = os.path.join(self.projectpath, l, 'LC_MESSAGES', 'django.po') + p = os.path.join(self.projectpath, l, 'LC_MESSAGES', 'django.po') if os.path.exists(p): self.languages_po[l] = polib.pofile(p) @@ -105,7 +73,7 @@ def save(self, *args, **kwargs): entry = polib.POEntry(msgid=msgid, msgstr=c) po.append(entry) - for k,p in self.languages_po.items(): + for k, p in self.languages_po.items(): p.save() path = os.path.join(self.projectpath, k, 'LC_MESSAGES', 'django.mo') p.save_as_mofile(path) @@ -118,10 +86,10 @@ def save(self, *args, **kwargs): return LinguaAdminForm def get_fieldsets(self, request, obj=None): - fields = super(LinguaModelAdmin,self).get_fieldsets(request,obj) - form = self.get_form(request, obj) + fields = super(LinguaModelAdmin,self).get_fieldsets(request, obj) + form = self.get_form(request, obj, fields=None) model = form._meta.model - fields[0][1]["fields"] += LinguaModelAdmin.get_fields(model) + fields[0][1]["fields"] += LinguaModelAdmin.get_admin_fields(request, model) return fields diff --git a/lingua/handler.py b/lingua/handler.py index f4257d7..35b7545 100755 --- a/lingua/handler.py +++ b/lingua/handler.py @@ -1,37 +1,6 @@ -""" -File: handlers.py - -Copyright (c) aquarianhouse.com | Georg Kasmin. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - from django.utils.translation import ugettext_lazy as _ + def post_init(sender, instance, **kwargs): """Class is ready, all attributes has been set """ """Loop through translation fields and set the gettext in beetween """ @@ -40,4 +9,3 @@ def post_init(sender, instance, **kwargs): value = getattr(instance, x) setattr(instance, x, _(value)) setattr(instance, "_".join((x,"00")), value)#original value - diff --git a/lingua/manage.py b/lingua/manage.py deleted file mode 100755 index 5e78ea9..0000000 --- a/lingua/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/lingua/management/commands/collectmessages.py b/lingua/management/commands/collectmessages.py index 91daaf6..1a05635 100755 --- a/lingua/management/commands/collectmessages.py +++ b/lingua/management/commands/collectmessages.py @@ -1,59 +1,55 @@ -""" -File: collectmessages.py - -Copyright (c) aquarianhouse.com | Georg Kasmin. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" from django.core.management import BaseCommand, CommandError -from django.db import models from django.utils.encoding import smart_str +from django.apps import apps +from django.db import models, router, connections, DEFAULT_DB_ALIAS +from collections import OrderedDict +from django.utils.translation.trans_real import all_locale_paths, check_for_language, language_code_re, to_locale +import gettext as gettext_module +import os +from django.core.files import File + class Command(BaseCommand): help = "Translate database messages" requires_model_validation = False + def add_arguments(self, parser): + parser.add_argument('--database', action='store', dest='database', + default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. ' + 'Defaults to the "default" database.') + def handle(self, *args, **options): db_values = [] - """ Taken from django.core.management.commands.syncdb""" - for app in models.get_apps(): - model_list = models.get_models(app, include_auto_created=True) - - """Performance is not so important, we do it once... """ + db = options.get('database') + connection = connections[db] + connection.prepare_database() + + # Build the manifest of apps and models that are to be synchronized + all_models = [ + (app_config.label, + router.get_migratable_models(app_config, connection.alias, include_auto_created=False)) + for app_config in apps.get_app_configs() + ] + + for app_name, model_list in all_models: for m in model_list: if hasattr(m, '_translation_fields'): for x in m._translation_fields: for y in m.objects.all(): - db_values.append( getattr(y, x) ) - - #print db_values - f = file('db_translation.html', "w") - """ blocktrans and we dont have to worry about to escape the string etc.""" - for v in db_values: - f.write('{%% blocktrans %%}%s{%% endblocktrans %%}\n' % smart_str(v)) - f.close() + db_values.append(getattr(y, x)) + + path = apps.app_configs[app_name].path + # print db_values + with open(os.path.join(path, 'extra_translations.py'), 'w') as f: + translations_file = File(f) + translations_file.write('''# encoding: utf-8 + +from __future__ import unicode_literals, absolute_import +from django.utils.translation import ugettext_lazy as _ + +translations = ( +''') + for v in db_values: + translations_file.write(""" _('%s'),\n""" % smart_str(v.replace('\'', '\\\''))) + translations_file.write(')') diff --git a/lingua/translation.py b/lingua/translation.py index 2fc2c58..2be5622 100755 --- a/lingua/translation.py +++ b/lingua/translation.py @@ -1,69 +1,37 @@ -""" -File: translation.py - -Copyright (c) aquarianhouse.com | Georg Kasmin. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" from django.db.models import signals import handler -from django.utils.translation import activate, deactivate from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from django.utils.translation import activate, deactivate +from django.db import models signals.post_init.connect(handler.post_init) -class Translation(object): - - def contribute_to_class(self, main_cls, name): - """Get translation fields and convert them then the class is ready - Django set the model fields as attributes - """ - from django.conf import settings - _languages = dict(settings.__dict__.get('LANGUAGES', {})) - translation_fields = [x for x in [x.lower() for x in self.__dict__ if '__' not in x] ] +class TranslationModel(models.Model): + def __init__(self, *args, **kwargs): - for x in translation_fields: - main_cls.add_to_class(x, self.__dict__[x]) + _languages = dict(filter(lambda x: x[0] != getattr(settings, 'LINGUA_DEFAULT', 'en'), + getattr(settings, 'LANGUAGES', ()))) + translation_fields = tuple([x for x in [x.lower() for x in self._translation_fields if '__' not in x]]) def _getattr(klass, name): if '_' in name: - lang, v = name.split('_')[::-1][0], name.split('_')[::-1][1] - + lang, v = name.split('_')[::-1][0], '_'.join(name.split('_')[:-1]) + if lang in _languages: activate(lang) - value = unicode(_(getattr(klass,v))) + value = unicode(_(getattr(klass, v))) deactivate() return value return klass.__class__.__getattribute__(klass, name) - main_cls.add_to_class('_translation_fields', tuple(translation_fields)) - main_cls.add_to_class('_languages', _languages) - main_cls.add_to_class('__getattr__', _getattr) + self.__class__.add_to_class('_translation_fields', translation_fields) + self.__class__.add_to_class('_languages', _languages) + self.__class__.add_to_class('__getattr__', _getattr) + + super(TranslationModel, self).__init__(*args, **kwargs) - contribute_to_class = classmethod(contribute_to_class) + class Meta: + abstract = True diff --git a/lingua/utils.py b/lingua/utils.py index f95f9c0..d0e17b0 100755 --- a/lingua/utils.py +++ b/lingua/utils.py @@ -1,41 +1,11 @@ -""" -File: utils.py - -Copyright (c) aquarianhouse.com | Georg Kasmin. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" from django.utils.translation import trans_real, activate -from django.utils.thread_support import currentThread import gettext + def clear_gettext_cache(): gettext._translations = {} trans_real._translations = {} trans_real._default = None - prev = trans_real._active.pop(currentThread(), None) - if prev: activate(prev.language()) \ No newline at end of file + prev = trans_real.get_language() + if prev: + activate(prev) diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..01fa693 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lingua.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5d9b91a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django==1.9.2 + diff --git a/setup.py b/setup.py index 3b0f8ee..10eb897 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,10 @@ setup( name='lingua', - version='0.8.2', + version='0.9.0', url='http://github.com/geomin/django-lingua', - maintainer='Georg Kasmin', - maintainer_email='georg@aquarianhouse.com', + maintainer='Guillaume Cisco', + maintainer_email='guillaumecisco@gmail.com', description='Django model translation on basis of gettext', classifiers=['License :: OSI Approved :: BSD License', 'Intended Audience :: Developers',