Package: django-translated-fields
Django model translation without magic-inflicted pain.Installation and usageAfter installingdjango-translated-fieldsin your Python
environment all you have to do is defineLANGUAGESin your
settings and add translated fields to your models:fromdjango.dbimportmodelsfromdjango.utils.translationimportgettext_lazyas_fromtranslated_fieldsimportTranslatedFieldclassQuestion(models.Model):question=TranslatedField(models.CharField(_("question"),max_length=200),)answer=TranslatedField(models.CharField(_("answer"),max_length=200),)def__str__(self):returnself.questionBasic usageModel fields are automatically created from the field passed toTranslatedField, one field per language.  For example, withLANGUAGES =[("en","English"),("de","German"),("fr","French")],
the following list of fields would be created:question_en,question_de,question_fr,answer_en,answer_de,
andanswer_fr.This implies that when changingLANGUAGESyou’ll have to runmakemigrationsandmigratetoo.Noquestionoranswermodel field is actually created. TheTranslatedFieldinstance is adescriptorwhich by default
acts as a property for the current language’s field:fromdjango.utils.translationimportoverridequestion=Question(question_en="How are you?",question_de="Wie geht es Dir?",question_fr="Ça va?",)# The default getter automatically returns the value# in the current language:withoverride("en"):assertquestion.question=="How are you?"withoverride("de"):assertquestion.question=="Wie geht es Dir?"# The default setter can also be used to set the value# in the current language:withoverride("fr"):question.question="Comment vas-tu?"assertquestion.question_fr=="Comment vas-tu?"TranslatedFieldhas afieldsattribute that returns a list of all
the language fields created.assertQuestion.answer.fields==["answer_en","answer_de","answer_fr"]For more attributes look at the``TranslatedField`` instance APIsection below.questionandanswercan only be used with model instances, they
do not exist in the database. If you want to use queryset methods which
reference individual translated fields you have to use language-specific
field names yourself. If you wanted to fetch only the english question
and answer fields you could do this as follows:questions=Question.objects.values_list("question_en","answer_en")Or better yet, using theto_attributehelper which automatically
uses the active language (if you don’t pass a specific language code as
its second argument):fromdjango.utils.translationimportoverridefromtranslated_fieldsimportto_attributewithoverride("en"):questions=Question.objects.values_list(to_attribute("question"),to_attribute("answer"))Changing field attributes per languageIt is sometimes useful to have slightly differing model fields per
language, e.g. for making the primary language mandatory. This can be
achieved by passing a dictionary with keyword arguments per language as
the second positional argument toTranslatedField.For example, if you add a language toLANGUAGESwhen a site is
already running, it might be useful to make the new language
non-mandatory to simplify editing already existing data through Django’s
administration interface.The following example addsblank=Trueto the spanish field:fromtranslated_fieldsimportTranslatedFieldclassQuestion(models.Model):question=TranslatedField(models.CharField(_("question"),max_length=200),{"es":{"blank":True}},)Overriding attribute access (defaults, fallbacks)There are no default values or fallbacks, only a wrapped attribute
access. The default attribute getter and setter functions simply return
or set the field for the current language (as returned bydjango.utils.translation.get_language). The default getter falls
back to the first language of the field in caseget_language()returnsNone. Apart from that the default getter has no
safetyfeatures and may raise anAttributeErrorand the setter might
set an attribute on the model instance not related to a model field.Both getters and setters can be overridden by specifying your ownattrgetterandattrsetterfunctions. E.g. you may want to
specify a fallback to the default language (and at the same time allow
leaving other languages’ fields empty):fromdjango.confimportsettingsfromtranslated_fieldsimportTranslatedField,to_attributedeffallback_to_default(name,field):defgetter(self):returngetattr(self,to_attribute(name),)orgetattr(self,# First language acts as fallback:to_attribute(name,settings.LANGUAGES[0][0]),)returngetterclassQuestion(models.Model):question=TranslatedField(models.CharField(_("question"),max_length=200,blank=True),{settings.LANGUAGES[0][0]:{"blank":False}},attrgetter=fallback_to_default,)Maybe you’re using locales with region codes such asfr-frwhere you
want to fall back to the language without a region code. An exampleattrgetterimplementation follows:fromtranslated_fieldsimportto_attributedeffallback_to_all_regions(name,field):defgetter(self):value=getattr(self,to_attribute(name),None)ifvalue:returnvaluereturngetattr(self,to_attribute(name,get_language().split("-")[0]))returngetterA customattrsetterwhich always sets all fields follows (probably
not very useful, but hopefully instructive):defset_all_fields(name,field):defsetter(self,value):forfieldinfield.fields:setattr(self,field,value)returnsetterTranslatedFieldinstance APITheTranslatedFielddescriptor has a few useful attributes (sticking
with the model and field from the examples above):Question.question.fieldscontains the names of all automatically
generated fields, e.g.["question_en","question_...",...].Question.question.languagesis the list of language codes.Question.question.short_descriptionis set to theverbose_nameof the base field, so that the translatable attribute can be nicely
used e.g. inModelAdmin.list_display.Using a different set of languagesIt is also possible to override the list of language codes used, for
example if you want to translate a sub- or superset ofsettings.LANGUAGES. Combined withattrgetterandattrsetterthere is nothing stopping you from using this field for a different kind
of translations, not necessarily bound todjango.utils.translationor even languages at all.Translated attributes without model field creationIf model field creation is not desired, you may also use thetranslated_attributesclass decorator. This only creates the
attribute getter property:fromtranslated_fieldsimporttranslated_attributes@translated_attributes("attribute","anything",...)classTest(object):attribute_en="some value"attribute_de="some other value"Model admin supportTheTranslatedFieldAdminclass adds the respective language to the
label of individual fields. Instead of three fields named “Question”
you’ll get the fields “Question [en]”, “Question [de]” and “Question
[fr]”. It intentionally offers no functionality except for modifying the
label of fields:fromdjango.contribimportadminfromtranslated_fieldsimportTranslatedFieldAdminfrom.modelsimportQuestion@admin.register(Question)classQuestionAdmin(TranslatedFieldAdmin,admin.ModelAdmin):pass# For inlines:# class SomeInline(TranslatedFieldAdmin, admin.StackedInline):#     ...As mentioned above, thefieldsattribute on theTranslatedFieldinstance contains the list of generated fields. This may be useful if
you want to customize various aspects of theModelAdminsubclass. An
example showing various techniques follows:fromdjango.contribimportadminfromdjango.utils.translationimportgettext_lazyas_fromtranslated_fieldsimportTranslatedFieldAdmin,to_attributefrom.modelsimportQuestion@admin.register(Question)classQuestionAdmin(TranslatedFieldAdmin,admin.ModelAdmin):# Pack question and answer fields into their own fieldsets:fieldsets=[(_("question"),{"fields":Question.question.fields}),(_("answer"),{"fields":Question.answer.fields}),]# Show all fields in the changelist:list_display=[*Question.question.fields,*Question.answer.fields]# Order by current language's question field:defget_ordering(self,request):return[to_attribute("question")]NoteIt’s strongly recommended to set theverbose_nameof fields when
usingTranslatedFieldAdmin, the first argument of most model
fields. Otherwise, you’ll get duplicated languages, e.g. “Question en
[en]”.Formsdjango-translated-fields provides a helper when you want form fields’
labels to contain the language code. If this sounds useful to you do
this:fromdjangoimportformsfromtranslated_fields.utilsimportlanguage_code_formfield_callbackfrom.modelsimportQuestionclassQuestionForm(forms.ModelForm):formfield_callback=language_code_formfield_callbackclassMeta:model=Questionfields=[*Question.question.fields,*Question.answer.fields]You may also globally configure language code labels to be shown within
a block:fromtranslated_fieldsimportshow_language_codedefview(request):form=...withshow_language_code(True):returnrender(request,"...",{"form":form})Please note that the response has to be rendered within theshow_language_codeblock. This doesn’t happen automatically when
using Django’sTemplateResponseobjects.Other featuresThere is no support for automatically referencing the current language’s
field in queries or automatically adding fields to admin fieldsets and
whatnot. The code required for these features isn’t too hard to write,
but it is hard to maintain down the road which contradicts my goal of
writinglow maintenance software. Still, feedback
and pull requests are very welcome! Please run the style checks and test
suite locally before submitting a pull request though – all that this
requires is runningtox.