Skip to content
electrolinux edited this page Mar 14, 2013 · 3 revisions

Some ideas about how to make bolt translations easy

Goal

Easing the work of the translators for providing an almost fully translated bolt in their favourite language. This proposal is, at least for now, only about translating bolt, not about providing a way to serve multi-language content. That's a totally different task.

Proposal

The translations are stored in 3 differents 'domains':

  • messages: almost all single line, short sentences are to be found here. It's in YAML format, the original english sentence is the key, and the value is the translation
  • contenttypes: contains additional strings generated for each contenttype
  • infos: this one is for larger block of text, where the key is a keyword, and the sentence can span many lines of text.

For the first two domains, 'messages' and 'contenttypes', a generic function, __(), is used to translate a string. It can be called either in php code or in Twig templates.

The variable parts of a string are marked with placeholders, and the values passed in a array, exactly the same way one call $app['translator']->trans() or $app['translator']->transChoice().

//php
$message = __("User %username% have been created.",array('%username%' => $username));

{# twig #}
<div>{{ __("User %username% have been created.",{'%username%':username}) }}</div>

Proposal for ContentType

If the function __() find a placeholder named %contenttype%, it will first replace it with the value passed as parameter, and try to find a translation for the result :

$message = __("View %contenttype%",array('%contenttype%' => $contenttype.singular_name));

will first try to translate, for example : "View page". If no translations is found, it will revert to the original translation call, "View %contenttype%"

The latest implementation looks also for %contenttypes% and do the same (expecting a pluralized name)

This specifics and dynamics translations are stored in a different domain, 'contenttypes'.

That way, we have a generic 'messages' domain with all the translatable strings gathered through the Php code and the templates, including all string containing the '%contenttype(s)%' placeholders, and an optional 'contenttypes' domain, where end users can put their preferred translations for each content-type they create/edit.

In locales/fr/messages.yml

"Dashboard": "Tableau de bord"
"View %contentype%": "Voir le %contenttype%"
"Edit %contenttype%": "Modifier le %contenttype%"
# ...

In locales/fr/contenttypes.yml:

"View page": "Voir la page"
"View foo": "Voir le truc"
"Edit page": "Modifier la page"
"Edit foo": "Modifier le truc"
#...

Each time the user create or edit a content-type, only the contenttypes domain is to update.

Gathering strings to translate

  • In Twig template, with handcrafted regexp... (fragile, need consistence in templates)
  • In Php code with token_get_all() (more robust)

The strings are merged with their previous translations and stored in the language domain 'messages'.

Generating dynamics %contenttypes% strings

  • For each string containing a %contenttype% placeholder, a set of strings are generated by replacing the placeholder with the singular_name of each contenttypes.

  • For each string containing a %contenttypes% placeholder, a set of strings are generated by replacing the placeholder with the name of each contenttypes.

Theses strings are then merged with their previous translations in the 'contenttypes' domain.

About the %contenttype% placeholder

There is some inconsistency in usage, as in dashboard.twig :

Latest {{ contenttype }}
More {{ contenttype }}
New {{ app.config.contenttypes[contenttype].singular_name }}

Where the two first are (lazily) using the 'slug' instead of the correct

app.config.contenttype[contenttype].name 

Recommended usage

use %contenttype% if it is replaced with $contenttype['singular_name'];

use %contenttypes% if it is replaced with $contenttype['name'];

don't use $contenttype when it's the key of the contenttypes array (as used for the slug)

(see i18n for twig template writers for details)

Translating big chunk of text

This is where the third domain, 'infos', come into action. This domain store the translations in a YAML file, but the keys are not the sentences, but a chosen keyword, as in about.twig :

  {{ app.translator.trans('about.bolt',{},'infos')|raw }}

  {{ app.translator.trans('about.licence',{'%paths_app%':paths.app},'infos')|raw }}

The fallback content, when no translation exists, is in the provided en/infos.en.yml file:

about:
    bolt: |
        <p>Bolt is a CMS that strives to be simple, fast, straightforward and enjoyable to use. Both for
            developers and content-editors. Bolt is Open Source, and as such it uses other Open Source
            components. If you are a developer you're very welcome to help in the further development of Bolt.
        </p>
    licence: |
        <p>All parts of Bolt are free to use under the open-source MIT license. The full licensing text can be
            found here, in the included <a href="%paths_app%LICENSE.txt">LICENSE.txt</a>.
        </p>

That's it for now.

(Comments welcome)