Localizing NSAttributedString with TML

NSAttributedString was introduced in iOS 3.2 to provide a decoration mechanism for NSString using custom attributes. Amongst other things, it allowed you to change colors and fonts of string fragments, as well as apply shadows and strokes.

Before iOS 3.2, developers had to come up with all kind of elaborate solutions to achieve this functionality. Usually it involved splitting strings into multiple parts and assembling precisely positioned sequences of UILabels. NSAttributedString made it way easier to apply decorations within a single string with fewer lines of code. You can now created an attributed string and apply it to a UILabel. Unfortunately, it did not provide any good way to localize and translate such strings without taking them apart and translating them outside of context. Translation Markup Language (TML) solves the problem by offering a powerful syntax that makes it easy to translate such decorated strings in context and with even fewer lines of code.

Let’s consider the following example where the word “World” needs to be indented using a bold font:

“Hello World

The quickest way to do this is to create an NSMutableAttributedString and indicate which portions of the string need to be bold:

[objc title=”Localization with TML”]

NSMutableAttributedString *str = [[NSMutableAttributedString alloc]
initWithString: @”Hello World”];

[str addAttribute: NSFontAttributeName
value: [UIFont boldSystemFontOfSize:12]
range: NSMakeRange(6, 5)];

label.attributedString = str;

[/objc]

Let’s localize the string using the standard NSLocalizedSting macro:

[objc title=”Localization with TML”]
NSMutableAttributedString *str = [[NSMutableAttributedString alloc]
initWithString: NSLocalizedString(@”Hello World”, @”Welcome message”)];

[str addAttribute: NSFontAttributeName
value: [UIFont boldSystemFontOfSize:12]
range: NSMakeRange(6, 5)];

label.attributedString = str;
[/objc]

There is clearly a problem here. The length of the words “Hello” and “World” will vary from language to language, so we cannot use hardcoded range for making the translated “World” bold.

A better approach would be to assemble the string from pieces:

[objc title=”Localization with TML”]
NSMutableAttributedString *str = [[NSMutableAttributedString alloc]
initWithString: NSLocalizedString(@”Hello “, @”As in Hello World”)];

[str appendAttributedString: [[NSAttributedString alloc]
initWithString: NSLocalizedString(@”World”, @”As in Hello World”)
attributes: @{NSFontAttributeName: [UIFont boldSystemFontOfSize:12]}
]];

label.attributedString = str;
[/objc]

This time, the string will be displayed correctly, but now we took both words out of context and translators will have to translate each word separately. This may be an acceptable solution in some simple examples, but in more complex cases, where words depend on the order and each other, this approach will not work.

Translation Markup Language (TML) solves the problem by introducing a more advanced macro:

TmlLocalizedAttributedString

Using TML, we get:

[objc title=”Localization with TML”]

NSAttributedString *str = TmlLocalizedAttributedStringWithTokens(@”Hello World“, (@{
@”bold”: @{@”font”: [UIFont boldSystemFontOfSize:12]}
}));

[/objc]

There is also a shorter notation for the “bold” decorator that uses square brackets:

[objc title=”Localization with TML”]

NSAttributedString *str = TmlLocalizedAttributedStringWithTokens(@”Hello [bold: World]”, (@{
@”bold”: @{@”font”: [UIFont boldSystemFontOfSize:12]}
}));
[/objc]

We can make the “font” attribute more JSON friendly so that it can be loaded/configured externaly.

[objc title=”Localization with TML”]

NSAttributedString *str = TmlLocalizedAttributedStringWithTokens(@”Hello [bold: World]”, (@{
@”bold”: @{
@”font”: @{
@”name”: @”system”,
@”size”: @12,
@”type”: @”bold”
}
}
}));

[/objc]

An even shorter version would be:

[objc title=”Localization with TML”]

NSAttributedString *str = TmlLocalizedAttributedStringWithTokens(@”Hello [bold: World]”, (@{
@”bold”: @{
@”font”: @”Helvetica-Bold, 12″
}
}));

[/objc]

We can add other attributes to the decoration definition using the same approach:

[objc title=”Localization with TML”]

NSAttributedString *str = TmlLocalizedAttributedStringWithTokens(@”Hello [bold: World]”, (@{
@”bold”: @{
@”font”: @”Helvetica-Bold, 12″,
@”color”: @”blue”
}
}));

[/objc]

The entire TML string can now be translated in context.

Notice, that you can change the definition of the decoration without loosing any translations. For example, the translation of the above string in Russian would be:

“Привет [bold: Мир]”

In Spanish:

“Hola [bold: Mundo]”

In RTL languages, like Hebrew and Arabic, you can still use the decorations in the same way:

“[bold: שלום [העולם”

To see a full list of attributes available for the TmlLocalizedAttributedString, visit our documentation at:

http://docs.translationexchange.com/ios/

Let’s take a look at a more complicated example. Suppose we are building a mobile app for runners, which tracks how many miles you run and at the end of the run displays a message that shows how many miles you have completed. We also want to indent the number of miles with a bold font:

“You have completed 4.2 miles on the last run.”

As in the previous example, if we were to try using NSMutableAttributedString, we would end up with some variation of the following code:

[objc title=”Localization with TML”]
NSDictionary *bold = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:@12]};

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] init];
[str appendString : NSLocalizedString(@”You have completed “, @”You have completed %d miles on your last run”)];

if (distance_in_miles == 1)
[str appendAttributedString:
[[NSAttributedString alloc] initWithString: NSLocalizedString(@”1 mile”, @”You have completed %d miles on your last run”)] attributes: bold];
else
[str appendAttributedString:
[[NSAttributedString alloc] initWithString:
[NSString stringWithFormat: NSLocalizedString(@”%d miles”, @”You have completed %d miles on your last run”), distance_in_miles]] attributes: bold];

[str appendString: NSLocalizedString(@” on your last run.”, @”You have completed %d miles on your last run”)];

[/objc]

This example has many issues:

  • The (distance_in_miles == 1) check fails for languages that have more complicated numeric rules, like Russian or Arabic.
  • “You have completed ” and ” on your last run” will been translated outside of the context of the entire sentence. In some languages some words must be switched around. So it fails in both contextualization and composition.
  • “1 mile” and “%d miles” are also translated outside of the context.

Using TML, the above code can be replaced with:

[objc title=”Localization with TML”]

TmlLocalizedAttributedStringWithTokens(
@”You have completed [bold: {count || mile}] on your last run.”,
(@{@”count”: @4.2, @”bold”: @{@”font”: @”Helvetica-Bold, 12″}})
)

[/objc]

The entire string is translated in context and numeric rules are properly applied for all languages.

What if the story was not referring to the viewer, but a friend of a viewer instead.

Anna has completed 4.2 miles on her last run.”

There is no clear way to accurately translate such string using the standard iOS libraries. In TML, this would simply be:

[objc title=”Localization with TML”]

TmlLocalizedAttributedStringWithTokens(
@”[bold: {user}] has completed [bold: {count || mile}] on {user | his, her} last run.”,
(@{@”user”: user_object, @”count”: @4.2, @”bold”: @{@”font”: @”Helvetica-Bold, 12″}})
)

[/objc]

Translation in Russian, would be:

“В своем последнем забеге [bold: {user}] {user | пробежал, пробежала} [bold: {count || милю, мили, миль}].”

That results in:

“В своем последнем забеге Anna пробежала 4.2 мили.”

Notice that the Russian translation completely reorganizes the order of the elements. It also provides accurate translations based on the gender of the user object and the numeric context based on the value of count.

To read more about Objective C integration and other token options and syntax, please visit our documentation:

http://docs.translationexchange.com/ios/

To try out our sample application, please visit:

https://github.com/translationexchange/tml-objc-samples

Get Started Today!

Create an account to get started now! No credit card required.

Get Started