Tradujok is a django db-based translation system with IETF's BCP 47 standard support and django-rest-framework serializers.
Tradukoj can be integrated with common js i18n libs thanks to his JSON tree generation feature.
This app is maintained and internally used by Develatio Technologies.
-
Python: >= 3.6
-
Django: >= 2.1, 2.2
- Namespaces to separate projects or big sections of your app
- JSON tree generation with your translations
- django-rest-framework urls/views/serializers
- django-rest-framework fields to be used in serializers
- Fallback translation for drf fields
- Translate model fields
- Languaje detection endpoint
- RTL - LTR support
- PO files Import/Export
- Public/Private translations isolation
-
Install
pip install django-tradukoj
-
Add "tradukoj" to INSTALLED_APPS:
INSTALLED_APPS = {
...
'tradukoj'
}
-
Include the tradukoj URLconf in urls.py:
url(r'^tradukoj/', include('tradukoj.urls'))
-
Run
python manage.py migrate
to create db records.
Usage of OneToOneTradukojField
:
from tradukoj.fields import OneToOneTradukojField
class MyModel(models.Model):
name = OneToOneTradukojField(null=True, blank=True, verbose_name='Name')
>>> from tradukoj.models import TranslationKey
>>> from myapp.models import MyModel
>>> instance = MyModel()
>>> # This creates a translatable key with automatically generated name
>>> instance.name = TranslationKey(init_namespace='mynamespace', public=True)
>>> instance.name
<TranslationKey: mynamespace.myapp_mymodel_name_0def4d78080144cdbc96302842935192>
>>> # This will add a translation for es-ES to instance.name
>>> # A record with 'es-ES' into tradukoj.models.BCP47 will be created if it
>>> # does not exist.
>>> instance.name.translate('es-ES', 'mi nombre')
>>> # Access translations:
>>> instance.name.translations.all()
<QuerySet [<Translation: (es-ES - español (Spanish)) mynamespace.myapp_mymodel_name_0def4d78080144cdbc96302842935192 => mi nombre>]>
>>> instance.name.translations.get(bcp47__langtag='es-ES')
<Translation: (es-ES - español (Spanish)) test.medicalscience_specialties_name_0def4d78080144cdbc96302842935192 => mi nombre>
>>> instance.name.translations.get(bcp47__langtag='es-ES').str_translation()
'mi nombre'
If you dont specify a key for translation, a random one will be generated. To
specify a key, you should pass it as argument to TranslationKey
:
>>> instance.name = TranslationKey(init_namespace='test', text='A translatable string', public=True)
>>> instance.name
<TranslationKey: test.A translatable string>
>>> instance.name.save()
>>> instance.name.translate('es-ES', 'Una cadena traducible')
>>> instance.name.translations.all()
<QuerySet [<Translation: (es-ES - español (Spanish)) test.A translatable string => Una cadena traducible>]>
If you are planning to use json tree feature of tradukoj, remember that a dot in key are handled as subobject.
TranslationKey(init_namespace='project', text='home.title', public=True)
TranslationKey(init_namespace='project', text='home.subtitle', public=True)
results into this json object:
{
es-ES: {
project: {
home: {
title: 'Bienvenido',
subtitle: 'Secciones'
}
}
},
en-US: {
project: {
home: {
title: 'Welcome',
subtitle: 'Sections'
}
}
}
}
- Languaje detection:
YOUR_API_URL/tradukoj/bestlangtag/
Asking fromes-ES;en
(spanish-Spain,english) a web with onlyes-MX
(spanish-Mexico) lang:
[
{
"langtag": "es-MX",
"score":92,
"accept_lang": "es-es"
},
{
"langtag": "es-MX",
"score": 92,
"accept_lang":"es"
},
{
"langtag":"en",
"score":100,
"accept_lang":"en"
}
]
- Available langs:
YOUR_API_URL/tradukoj/available/
{
"count": 28,
"next": null,
"previous": null,
"results": [
{"langtag":"en-EN","name":"English (English)","best_match_score":92,"default":false,"direction":0},
{"langtag":"es-MX","name":"español (Spanish)","best_match_score":92,"default":false,"direction":0},
{"langtag":"en-US","name":"English (English)","best_match_score":98,"default":false,"direction":0},
{"langtag":"ar-LB","name":"العربية (Arabic)","best_match_score":0,"default":false,"direction":1},
{"langtag":"fr-FR","name":"français (French)","best_match_score":0,"default":false,"direction":0},
{"langtag":"pt-PT","name":"português (Portuguese)","best_match_score":0,"default":false,"direction":0},
{"langtag":"ru-RU","name":"русский (Russian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"de-DE","name":"Deutsch (German)","best_match_score":0,"default":false,"direction":0},
{"langtag":"it-IT","name":"italiano (Italian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"hu-HU","name":"magyar (Hungarian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"cs-CZ","name":"čeština (Czech)","best_match_score":0,"default":false,"direction":0},
{"langtag":"zh-CN","name":"中文 (Chinese)","best_match_score":0,"default":false,"direction":0},
{"langtag":"vi-VN","name":"Tiếng Việt (Vietnamese)","best_match_score":0,"default":false,"direction":0},
{"langtag":"hi-IN","name":"हिन्दी (Hindi)","best_match_score":0,"default":false,"direction":0},
{"langtag":"uk_UA","name":"українська (Ukrainian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"hy_AM","name":"հայերեն (Armenian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"be_BY","name":"беларуская (Belarusian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"bg_BG","name":"български (Bulgarian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"km_KH","name":"ខ្មែរ (Khmer)","best_match_score":84,"default":false,"direction":0},
{"langtag":"et_EE","name":"eesti (Estonian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"lv_LV","name":"latviešu (Latvian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"pl_PL","name":"polski (Polish)","best_match_score":0,"default":false,"direction":0},
{"langtag":"ro_RO","name":"română (Romanian)","best_match_score":0,"default":false,"direction":0},
{"langtag":"uz_Latn","name":"o‘zbek (Uzbek)","best_match_score":0,"default":false,"direction":0},
{"langtag":"tr_TR","name":"Türkçe (Turkish)","best_match_score":0,"default":false,"direction":0},
{"langtag":"th_TH","name":"ไทย (Thai)","best_match_score":0,"default":false,"direction":0},
{"langtag":"zh_TW","name":"中文 (Chinese)","best_match_score":0,"default":false,"direction":0},
{"langtag":"az_AZ","name":"azərbaycan (Azerbaijani)","best_match_score":0,"default":false,"direction":0},
]
}
- Get all public translations list:
YOUR_API_URL/tradukoj/public/
- Get all private translations list:
YOUR_API_URL/tradukoj/private/
- Get plain JSON of all public translations:
YOUR_API_URL/tradukoj/public/plain/
- Get plain JSON of all private translations:
YOUR_API_URL/tradukoj/private/plain/
- Get filtered public translations:
YOUR_API_URL/tradukoj/public/plain/?bcp47__langtag=es&key__namespace__text=mynamespace
- Get filtered private translations:
YOUR_API_URL/tradukoj/public/plain/?bcp47__langtag=es&key__namespace__text=mynamespace
To handle a model translatable field into your drf serializers, TradukojSerializedField
from tradukoj.drf_fields import TradukojSerializedField
class MySerializer(serializers.ModelSerializer):
name = TradukojSerializedField(read_only=True, fallback_langtag='en-US')
class Meta:
model = MyModel
exclude = ('id', )
This will give you a JSON like:
{
name: {
'fallback': 'Welcome',
'text': 'mykey',
'translations': {
'es-ES': 'Bienvenido',
'en-US': 'Welcome'
}
}
}
usage: manage.py generate_pofile [-h] --langtag LANGTAG --output OUTPUT
--namespace NAMESPACE --reference_langtag
REFERENCE_LANGTAG [--version] [-v {0,1,2,3}]
[--settings SETTINGS]
[--pythonpath PYTHONPATH] [--traceback]
[--no-color] [--force-color]
Generate a .po file of langtag
optional arguments:
-h, --help show this help message and exit
--langtag LANGTAG tag used in BCP47
--output OUTPUT output file
--namespace NAMESPACE
namespace of translations
--reference_langtag REFERENCE_LANGTAG
translated text used in comments to help translator
team
--version show program's version number and exit
-v {0,1,2,3}, --verbosity {0,1,2,3}
Verbosity level; 0=minimal output, 1=normal output,
2=verbose output, 3=very verbose output
--settings SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Raise on CommandError exceptions
--no-color Don't colorize the command output.
--force-color Force colorization of the command output.
Generate a .po file of es-ES
and put en-US
translations as a commentary of
every msgid
:
python manage.py generate_pofile --langtag es-ES --output ./pofile.po --namespace mynamespace --reference_langtag en-US
This will destroy those keys not in a .po file and related translations.
usage: manage.py destroy_dbkeys_not_in_pofile [-h] --pofile POFILE --namespace
NAMESPACE [--safe] [--version]
[-v {0,1,2,3}]
[--settings SETTINGS]
[--pythonpath PYTHONPATH]
[--traceback] [--no-color]
[--force-color]
Remove those translations key in DB that does not exists in a given pofile
optional arguments:
-h, --help show this help message and exit
--pofile POFILE The pofile
--namespace NAMESPACE
The namespace
--safe Show those keys that should be deleted but do not
commit delete command on db
--version show program's version number and exit
-v {0,1,2,3}, --verbosity {0,1,2,3}
Verbosity level; 0=minimal output, 1=normal output,
2=verbose output, 3=very verbose output
--settings SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Raise on CommandError exceptions
--no-color Don't colorize the command output.
--force-color Force colorization of the command output.
If you are planning to use JSON tree generation, some keys will be incompatible with this scheme:
- namespace.section.title = 'test'
- namespace.section = 'test'
to detect those incompatibility, use show_incompatible_tree_key
command:
usage: manage.py show_incompatible_tree_key [-h] [--pofile POFILE] [--version]
[-v {0,1,2,3}]
[--settings SETTINGS]
[--pythonpath PYTHONPATH]
[--traceback] [--no-color]
[--force-color]
Show incompatible Translation Key for plain tree generation
optional arguments:
-h, --help show this help message and exit
--pofile POFILE Test pofile instead database
--version show program's version number and exit
-v {0,1,2,3}, --verbosity {0,1,2,3}
Verbosity level; 0=minimal output, 1=normal output,
2=verbose output, 3=very verbose output
--settings SETTINGS The Python path to a settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath PYTHONPATH
A directory to add to the Python path, e.g.
"/home/djangoprojects/myproject".
--traceback Raise on CommandError exceptions
--no-color Don't colorize the command output.
--force-color Force colorization of the command output.
Importing tradukoj.admin
models into your admin, will give you some features
like importing .po files.
from tradukoj import models
class TranslationKeyAdmin(admin.ModelAdmin):
search_fields = ('text',)
class TranslationAdmin(admin.ModelAdmin):
search_fields = ('key__text',)
admin.site.register(models.TranslationKey, TranslationKeyAdmin)
admin.site.register(models.GetTextFile)
admin.site.register(models.Namespace)
admin.site.register(models.Translation, TranslationAdmin)
admin.site.register(models.BCP47)
Assuming that you are storing your current languaje tag in store.state.i18n.active_langtag
,
use this POC vuejs code:
create a plugins/tradukoj-translate.js
with this content:
import store from "../store";
const TradukojTranslatable = {
install(Vue) {
Vue.mixin({
methods: {
$tradukojTranslate(translatable) {
if (!translatable) {
return "";
}
if (
translatable.translations &&
translatable.translations[store.state.i18n.active_langtag]
) {
return translatable.translations[
store.state.i18n.active_langtag
];
}
if (translatable.fallback) {
return translatable.fallback;
}
return "";
}
}
});
}
};
export default TradukojTranslatable;
Register globally:
import TradukojTranslatable from "@/plugins/tradukoj-translatable";
Vue.use(TradukojTranslatable);
Use in component:
<template>
<div>
<p>{{ $tradukojTranslate(mymodel.name) }}</p>
</div>
</template>
[...]
Once you change the value of store.state.i18n.active_langtag
, the translations will be
automatically updated to current selected lang.