Tml

Translation Markup Language

Translation Markup Language (TML) is used to identify the non-translatable or 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.

Basics

Tml Client SDK extends Controller and View helpers by providing a translation function, called "tr". The function can be called using any of the following ways:

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.

Let's start with a sample phrase:

tr("Hello World")

The description of a phrase 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. As a general rule, you should always provide description to words, phrases and sentences that are only meaningful within a specific context. Tml 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.

Data Tokens

In many cases your tokens would be string objects that get substituted directly into the translated sentence.

The problem with the above example, is that the "registration page" link text would be translated differently based on the context where it appears. You must keep the two parts together to make sure the translations are accurate. You will later see how you can use decoration tokens to fix the above problem.

If your translation key needs to use context rules, you can pass the object and the substitution value as an array.

tr("Dear {user}", :user => [@michael, "Michael B."])
Dear Michael B.

You can also get the substitution value by invoking a method of the object by using a symbol in the second parameter.

The object itself may be a hash too. But since hashes don't provide a mechanism to extract a meaningful string value, make sure that you indicate what the substitution value should be. You can pass it as a value attribute.

Methon Tokens

Method tokens allow you to invoke methods on the object you are passing to get the substitution value.

tr("Hello {user.name}, you are a {user.gender}", :user => @michael)
Hello Michael, you are a male

Piped Tokens

Piped tokens work in conjunction with context rules and allow you to provide substitution values based on the object values.

tr("You have {count || one: message, other: messages}", :count => 1)
You have 1 message
You have 2 messages
You have 10 messages

Double pipe "||" means that the value would be displayed, followed by the word that depends on the value. In this case, if the count value meets the criteria for the rule "one", then it will display the word set to the rule. For all other cases it would display the "other" value.

Since the sequence of parameters is mapped to the sequence of rules, you can omit naming the parameters.

tr("You have {count || message, messages}", :count => 2)
You have 1 message
You have 2 messages
You have 10 messages

Since the library comes with default pluralizers, you don't even need to provide the plural form. It will be automagically generated for you.

tr("You have {count || message}", :count => 2)
You have 1 message
You have 2 messages
You have 10 messages

The same exact concept applies to other token types and context rules.

Even though the base language does not have a gender specific dependency in some cases, it is always good to wrap it with an implied token.

tr("{user| Born on}: ", :user => @michael)
Born on:

As a general rule, if any of the words of your translation keys depend on a user, use implied tokens. It won't affect default translations, yet it would give translators an option make the translation accurate.

Decoration Tokens

Decoration tokens are used to inject HTML styling into translations. In other libraries, like in iOS or Android, the tokens can be substituted with a native decoration framework.

Context Rules

Context rules are used to provide translations based on a specific value of one or more tokens in a sentence.

Number

Languages may have simple or complex numeric rules. For example, in English, there are only two rules for "one" and "other". Slovak languages, like Russian, have 3 rules. Translator can provide a translation for each rule or rule combination based on the token values.

1.upto(3) do |i|
tr("You have {count || message}", :count => i)
end
You have 1 message
You have 2 messages
You have 3 messages

Lists

In languages like Hebrew, the list rules may include cases such as when all members of the list are male, female or have mixed genders. In Russian, the list rules may include cases for single member male, female, unknown or multiple members.

[[@michael], [@michael, @anna]].each do |users|
tr("{users || likes, like} this post", :users => users)
end
Michael likes this post
Anna likes this post
Karen and Jenny like this post
Thomas, Michael and Peter like this post

[[@michael], [@anna], [@karen, @jenny], [@thomas, @michael, @peter]].each do |users|
tr("{users||has, have} arrived at {users|his, her, their} destination.",
:users => [users, lambda{|user| tr(user.name)}]
)
end
Michael has arrived at his destination.
Anna has arrived at her destination.
Karen and Jenny have arrived at their destination.
Thomas, Michael and Peter have arrived at their destination.

Language Cases

Language cases allow you to manipulate the values of the data passed through tokens.

Counters

1.upto(3) do |index|
tr("You have already sent this message {count::times}", :count => index)
end
You have already sent this message 1 times
You have already sent this message 2 times
You have already sent this message 3 times

Ordinals

1.upto(4) do |index|
tr("This is your {count::ord} warning", :count => index)
end
This is your 1st warning
This is your 2nd warning
This is your 3rd warning
This is your 4th warning

Extensions

Blocks

Blocks allow you to apply a set of options to all translation keys within the block. One of the main uses cases for the blocks are grouping keys into sources. Sources are used for caching and management of translation keys.