sl10n is a library that takes a unique approach to dealing with localization by using statically typed translation keys.
- Type-safe: we use statically typed translation keys, so you don't have to worry about making a typo or using a wrong key.
- Explicit: you get the exact result that you expect.
- Versatile: you can use it in any project.
- Self-sufficient, no other tools required.
- Written purely on Python.
- Easy to use.
- Small.
Imagine you have a lang
folder
containing all our translation files and a parser
that reads these files and stores them in a mapping.
{
"my_key_1": "My text",
"my_key_2": "Wrong text"
}
import parser # your handwritten parser
locales = parser.parse('/lang')
locale = locales.get('en')
print(locale.get('my_key_1')) # My text
You may probably think "Sounds pretty simple" and you'd be right. This approach is pretty common (even Minecraft uses it).
But it's really error-prone. You can easily make a typo or refer to a different key with similar name.
print(locale.get('my_key_I')) # my_key_I
print(locale.get('my_key_2')) # Wrong text
This becomes a real trouble once you change the schema of your translation files and even IDEs won't help you.
sl10n fixes this by introducing locale containers.
In short, a locale container is a spec of your translation files that contains all possible keys. At parsing, all your localization gets collected into locale containers, so you can access them freely.
sl10n defines a base class for your locale container (SLocale
)
and a parsing system (SL10n
).
from sl10n import SL10n, SLocale
class MyLocale(SLocale): # your locale container, it MUST inherit from SLocale
my_key_1: str
my_key_2: str
l10n = SL10n(MyLocale, 'lang') # creating a reference for system
l10n.init() # execute parser
The key difference is that now you can access your translated strings as class attributes.
locale: MyLocale = l10n.locale('en') # returns default one if this language wasn't found
print(locale.my_key_1) # My text
That way, your IDE can suggest what keys you can use and also tell you if you made a typo.
print(locale.my_key_I) # Unresolved attribute reference 'my_key_I for class 'MyLocale'
You can still access your locale container dynamically if you want (e.g. when the key is known only at runtime).
key = f() # returned 'my_key_1'
print(locale.get(key)) # My text
-
If your translation files don't follow the spec -
SL10n
will notify you and also try to fix it:- add all undefined keys
- move all unexpected keys to the bottom of the file
-
You can also create new translation files:
from sl10n import SL10n, SLocale class MyLocale(SLocale): my_key_1: str my_key_2: str l10n = SL10n(MyLocale, 'lang') l10n.create_lang_file('de') # copy the contents of default language file ('en') to a new file
-
Define what filenames should be ignored:
l10n = SL10n(MyLocale, 'lang', ignore_filenames=['config', 'tags'])
-
Make a custom parsing implementation:
from sl10n import SL10n from sl10n.pimpl import ParsingImpl import yaml class YAMLImpl(ParsingImpl): file_ext = 'yml' def __init__(self, module=yaml, *args, **kwargs): self.module = module self.args = args self.kwargs = kwargs def load(self, file): return self.module.load(file, self.loader) # yaml.load and yaml.dump are not safe def dump(self, data, file): self.module.dump(data, file, self.dumper, *self.args, **self.kwargs) ... l10n = SL10n(MyLocale, 'lang', parsing_impl=YAMLImpl())
-
Apply some modifiers to your file (todo: make a docs explaining modifiers):
{ "my_key_1": "My text", "my_key_2": "Wrong text", "$redump": true // redumps the file anyway }
{ "my_key_1": "My text", "my_key_2": "<not finished>", "$exclude": true // excludes the file from parsing }