Skip to content

6. Tr8n Syntax and Translation Markup Language

berk edited this page Apr 5, 2012 · 1 revision

Translation Markup Language (TML) is used to identify the non-translatable and dynamic data within the labels. It provides a way to mark data and decoration tokens within the strings that need to be translated. There are different types of applications that can use TML - web, mobile and desktop. Some use HTML, others use Wiki-Like syntax for decorating the labels. TML aims at abstracting out the decoration mechanisms of the string used by the applications and instead provides its own simple, but powerful syntax. This allows for translation sharing across multiple applications.

There are two flavors of the translation method signature in Tr8n and both are supported in all Tr8n Client SDKs:

tr(label, description = "", tokens = {}, options = {})

and

tr(label, options = {})

“label” - is the only required parameter in both cases. “description” - is an optional parameter, but should always be used if the label by itself is not sufficient enough to provide the meaning of the phrase. “tokens” - is an optional parameter that contains a hash (or a dictionary) of token values to be substituted in the label. “options” - provides a mechanism for passing additional directives to the translation engine.

Here are examples on how to use the above methods:

tr("Hello {user}!", "Welcome message", {:user => current_user.name}, {:locale => "en-US"})

or

tr("Hello {user}!", {:description => "Welcome message", :tokens => {:user => current_user.name}, :locale => "en-US"})

In addition to these functions, any language that can extend the String class, should extend it with a translate method.

For example, in Ruby, it would look something like this:

"Hello {user}!".translate("Welcome message", {:user => current_user.name}, {:locale => "en-US"})

In Objective C, this would look like:

[@"Hello {user}!" translateWithDescription: @"Welcome Message" tokens: [NSDictionary dictionaryWithObjectsAndKeys: @"Michael", @"user", nil] options: [NSDictionary dictionaryWithObjectsAndKeys: @"en-US", @"locale" nil];

Providing the locale in options parameter is used only as an example and is not necessary as Tr8n engine knows what the current language is. It is useful in some cases - when your application has more than one default language. For example, some Russian sites that use Tr8n have mixed default languages, some parts are in English and other parts are in Russian.

The following is the simplest example of an internationalized phrase:

<%= tr("Hello World") %>

or alternatively:

<%= "Hello World".translate %>

The DESCRIPTION is not mandatory, but it should be used in cases when the label alone is not sufficient enough to determine the meaning of the sentence being translated. Tr8n translation engine uses label and description together to create a unique key for each phrase. The description serves two purposes: it creates a unique key for each label and it also gives a hint to the translators for the context in which the label is used. For example, the following two phrases will be registered as two independent entries in a database even though the have the same label, but a different description. The user will have to translate each one of them separately as they will have different translated labels in other languages.

<%= tr("Invite", "Link to invite your friends to join the site") %>
<%= tr("Invite", "An invitation you received from your friend") %>

It is important to provide the best possible description for each phrase from the start. Keep in mind that changing a description in the future, after it has already been translated, will register a new phrase in the database and invalidate all of its translations. On the other hand, labels that are complete sentences may not need a description as they are fully self-contained.

There are a number of other flavors of the “tr” function:

<%=image_tag(image_url, :alt => trl("Image description")) %>

Trl is simply an extension to the tr method with an option to hide decorations so that labels, list options, alt tags can be translated:

<%=image_tag(image_url, :alt => tr("Image description")) %>

Users can translate those values in the bulk mode.

trfe and trfn are controller methods for translating flash errors and flash notices.

# for translating labels
def trl(label, desc = "", tokens = {}, options = {})
  tr(label, desc, tokens, options.merge(:skip_decorations => true))
end

# flash notice
def trfn(label, desc = "", tokens = {}, options = {})
  flash[:trfn] = tr(label, desc, tokens, options)
end

# flash error
def trfe(label, desc = "", tokens = {}, options = {})
  flash[:trfe] = tr(label, desc, tokens, options)
end
# end translation helper methods

If you need to translate something in a controller, you can use:

class SomeController < ApplicationController
  def some_action
    @some_value = tr("Value to be translated")
    trfn("Action completed!")
  end
end

If you need to translate something in a model, you can use any of the following:

class SomeModel < ActiveRecord::Base
  def some_method
    @some_value = "Value to be translated".translate
    @some_value = Tr8n::Language.translate("Value to be translated")
    @some_value = Tr8n::Language.for("ru").translate("Value to be translated")
  end
end

The following is a short summary of the Tr8n’s label internationalization notations.

Tr8n Token Types

It would have been boring if all of the labels in a site were just simple sentences without any dynamic data or decorations. Tr8n tokens are there to support the dynamic information in a label.

There are two main token types of tokens in the Tr8n syntax:

  • data tokens

  • decoration tokens

Data tokens are strings surrounded by the curly brackets inside of a label. So anything of this form ”‘{TOKEN_NAME}”’ is considered a data token.

Decoration tokens are strings surrounded by squared brackets inside of a label. So anything of this form ”‘[TOKEN_NAME: value to be decorated]”’ is considered a decoration token.

There are a number of different flavors of data tokens. Below are some of the flavors with some examples.

Data Tokens

Simple Data Tokens

There are a number of ways to substitute a data token with a value. Below are some of the main examples. Simple string substitution:

<%= tr("Dear {user}", "Fragment sample", :user => current_user) %>

The to_s function will be applied on the value of the current_user variable and substituted into the {user} token. Value substitution:

<%= tr("Dear {user}", "Fragment sample", :user => [current_user, display_user(current_user)]) %>

Notice one important thing that current_user is passed as a first element in the array. This is done for gender rules evaluation - i will describe this in the later sections. The second value is the actual value we want to subsitute into the {user} token. Value substitution using symbol method call:

<%= tr("Dear {user}", "Fragment sample", :user => [current_user, :first_name]) %>

As in the previous example, first object for rules engine, second is a symbol that represents a method that will be called on the object and the result will be placed into the {user} token. Value substitution using symbol method call with parameters:

<%= tr("Dear {user}", "Fragment sample", :user => [current_user, :some_method, "value"]) %>

Same as the above example, but the method can be called with some parameters. Value substitution using lambda method call:

<%= tr("Dear {user}", "Fragment sample", :user => [current_user, lambda{|val| html_for(val)}]) %>

The second parameter can also be a lambda. In that case current_user will be passed as a val into the lambda and the result of the lambda evaluation will be placed into the {user} token. Value substitution using lambda method call with parameters:

<%= tr("Dear {user}", "Fragment sample", :user => [current_user, lambda{|val, test| html_for(val, test)}], "test"]) %>

Same as the above, but lambda has some additional parameters.

You may be wondering why we need so many variations. Well, they are all useful, as you will see later.

Method Tokens

Method token allows you to call a method on a token itself. It is useful if you have multiple method calls on the same token in one sentence. Consider the following example:

<%= tr("Dear {user.first_name} {user.last_name}", "Fragment sample", :user => current_user) %>

Since the substitution is implied in the token definition itself, you don’t have to use any of the basic data token substitution forms.

Hidden Tokens

Hidden tokens are used primarily for the default language dynamic data substitution that would not make sense in the translated label. Hidden tokens will not appear as tokens when translator opens the translation dialog. Consider the following examples:

<%= tr("{user} changed {_his_her} name", "Fragment sample", :user => current_user, :_his_her => current_user.his_her) %>
<%= tr("you have {count} {_messages}", "Fragment sample", :count => NUM, :_messages => "message".pluralize_for(NUM)) %>

In the first cases _his_her will be substituted in English to the appropriate phrase based on the current_user gender. And in the second case the correct form of message will be used based on whether the number is 1 or other.

There is a better approach for the above examples, using the Transform tokens. But there may still be situations when hidden token can be useful.

Transform tokens

Transform tokens are used together with rules defined in the rules engine. Their primary job is to provide shortcuts for rule based tokens in the site native language. Consider the following example:

<%= tr("{user} changed {user| his, her} name", "Fragment sample", :user => current_user) %>

Notice that the hidden token has been replaced by the transform token. Keep in mind that {user} token must be registered as a gender based token. Consider another example:

<%= tr("You have {count|| message, messages}", "Fragment sample", :count => messages.size) %>

Notice that, in this case, {count} must be registered as a number based token. When the native language rule is evaluated it will use the singular or plural form of the word “message” based on the value of count. A single pipe indicates that the comma delimited word(s) that follow it are dependent on the token value, but the token itself should not be displayed. A double pipe indicates that the value of the token should be displayd as well. You don’t have to provide the plural form of the word if it can be derived from a singular form. So {count|| message} will be good enough and it will be pluralized by the engine automatically. Every rule has support for a transform token. For example:

<%= tr("Michael {date| turned, turns, will turn} 33 on {date}", "Fragment sample", :date => some_date) %>

This is the case when piped token is used for a date token object. And one more example for a list:

<%= tr("{users|| likes, like} this link", "Fragment sample", :users => [users_list, lambda{|user| user.first_name}]) %>

In this case if the users array contains a single element it will use the first form, otherwise it will use the plural form.

Decoration Tokens

Decoration tokens are used to decorate some text in a label. Consider the following example:

<%= tr("[link: Click here] to visit our site", "Fragment sample", :link => lambda{|text| link_to(text, 'http://www.google.com')}) %>

When this label is rendered “Click here” will be turned into a link.

Similarly you can do any kind of HTML decorations inside of the label. Alternative ways for using decorations are:

<%= tr("[link: Click here] to visit Google", "Fragment sample", :link => "<a href='http://www.google.com'>{$0}</a>") %>

{$0} is the translated value of the link. And another way, using a default decoration:

<%= tr("[link: Click here] to visit Google", "Fragment sample", :link => ['http://www.google.com']) %>

If you prefer to have hashes instead of arrays, you can do the following as well:

<%= tr("[link1: Click here] to visit Google", "Fragment sample", :link1 => {href => 'http://www.google.com'}) %>

The default decorations are defined in the config/tr8n/tokens/decorations.yml file. The are presented in the following form:

bold: "<strong>{$0}</strong>"
italic: "<i>{$0}</i>"
link: "<a href='{$1}' style='{$2}'>{$0}</a>"
link1: "<a href='{$href}' style='{$style}'>{$0}</a>"

You can edit the file and add as many of the default decorations as you need. Notice that you do not need to provide token parameters for decorations that only use {$0} token. So this would work just fine:

<%= tr("This is some [bold: very cool] stuff!") %>