Product
Services
Solutions
BlogDocs
Back

Universal Placeholders

Universal placeholders allow you to work with one set of translations for all platforms, automatically converting them to the appropriate format during export.

Why do you need universal placeholders?

The problem

Imagine you have an application for three platforms with the same message:

Web (JSON):

{"welcome": "Hello, {{username}}! You have {{count}} new messages."}

iOS (.strings):

"welcome" = "Hello, %@! You have %li new messages.";

Android (XML):

<string name="welcome">Hello, %s! You have %d new messages.</string>

Without universal placeholders, you need to translate the same text three times, spend triple the budget, and manually ensure consistency.

The solution2

With universal placeholders:

  • Translate once for all platforms

  • Automatic conversion during export

  • Consistent translations everywhere

  • Save time and money

Universal format

Localit.io uses a unified format with square brackets:

Basic types

Type

Format

Example

String

[%s]

[%s], [%1$s], [%s:name]

Integer

[%d]

[%d], [%1$d], [%d:count]

Float

[%f]

[%f], [%.2f], [%1$.2f:price]

Named parameters

Add :name after the type for convenience:

  • [%s:username] — string with name

  • [%d:count] — number with name

  • [%.2f:price] — float with name and precision

Export conversion

Source text in Localit.io:

"Hello, [%s:username]! You have [%d:count] new messages."

Result for different platforms:

Platform

Result

Web (i18n)

"Hello, {{username}}! You have {{count}} new messages."

iOS

"Hello, %@! You have %li new messages."

Android

"Hello, %s! You have %d new messages."

Web (ICU)

"Hello, {username}! You have {count} new messages."

Web (.NET)

"Hello, {0}! You have {0} new messages."

Web (Symfony)

"Hello, %username%! You have %count% new messages."

Import with auto-conversion

When uploading files, enable the "Convert to universal placeholders" option.

Before upload (iOS):

"welcome" = "Hello, %@! You have %li messages.";

After upload:

"welcome" = "Hello, [%s]! You have [%d] messages.";

Practical example

Let's look at a real scenario: you have a mobile shopping app with a web version. You need to localize order notifications.

Source files

Web application (messages.json):

{"order_status": "Your order {{orderId}} for {{amount}} is ready for pickup!","delivery_time": "Delivery will take {{hours}} hours."}

iOS application (Localizable.strings):

"order_status" = "Your order %@ for %.2f is ready for pickup!";
"delivery_time" = "Delivery will take %li hours.";

Android application (strings.xml):

<?xml version="1.0" encoding="UTF-8"?><resources>
    <string name="order_status">Your order %s for %.2f is ready for pickup!</string>
    <string name="delivery_time">Delivery will take %d hours.</string></resources>

Upload to Localit.io

  1. Create a project and select "English" as source language

  2. Upload first file (messages.json):

    • Choose "JSON" format

    • Enable "Convert to universal placeholders"

    • Click "Upload"

  3. Upload second file (Localizable.strings):

    • Choose "iOS Strings" format

    • Enable "Convert to universal placeholders"

    • Click "Upload"

  4. Upload third file (strings.xml):

    • Choose "Android XML" format

    • Enable "Convert to universal placeholders"

    • Click "Upload"

Result in editor

After upload, Localit.io will automatically merge keys and attempt to convert placeholders:

Initial result:

order_status = "Your order [%1$s:orderId] for [%1$s:amount] is ready for pickup!"
delivery_time = "Delivery will take [%1$s:hours] hours."

Problem: Placeholder types for different platforms are incompatible. JSON uses named string placeholders, while iOS and Android use typed ones with positional numbers. The system automatically detected all as string [%1$s].

Manual placeholder editing

You need to fix data types manually:

  1. orderId remains string: [%1$s:orderId][%s:orderId]

  2. amount should be float: [%1$s:amount][%.2f:amount]

  3. hours should be integer: [%1$s:hours][%d:hours]

Final result:

order_status = "Your order [%s:orderId] for [%.2f:amount] is ready for pickup!"
delivery_time = "Delivery will take [%d:hours] hours."

Now you have properly typed universal placeholders that export correctly for all platforms.

Translate to French

Translate keys once:

order_status = "Votre commande [%s:orderId] pour [%.2f:amount] est prête à récupérer!"
delivery_time = "La livraison prendra [%d:hours] heures."

Export for each platform

Export for web (JSON with i18n placeholders):

  1. Go to "Export" section

  2. Select "French" language

  3. Choose "JSON" format

  4. In settings select "i18n placeholders"

  5. Click "Download"

Result (messages_fr.json):

{"order_status": "Votre commande {{orderId}} pour {{amount}} est prête à récupérer!","delivery_time": "La livraison prendra {{hours}} heures."}

Export for iOS (.strings with iOS placeholders):

  1. Choose "iOS Strings" format

  2. In settings select "iOS placeholders"

  3. Click "Download"

Result (fr.lproj/Localizable.strings):

"order_status" = "Votre commande %@ pour %.2f est prête à récupérer!";
"delivery_time" = "La livraison prendra %li heures.";

Export for Android (XML with printf placeholders):

  1. Choose "Android XML" format

  2. In settings select "Printf placeholders"

  3. Click "Download"

Result (values-fr/strings.xml):

<?xml version="1.0" encoding="UTF-8"?><resources>
    <string name="order_status">Votre commande %s pour %.2f est prête à récupérer!</string>
    <string name="delivery_time">La livraison prendra %d heures.</string></resources>

Savings

Without universal placeholders:

  • 6 keys to translate (2 for each platform)

  • Risk of translation inconsistencies

  • Need to synchronize changes

With universal placeholders:

  • 2 keys to translate

  • Automatic consistency

  • One place for making changes

Result: 67% time savings on translations and 100% consistency guarantee.

Export settings

In the "Placeholder format" section, choose:

  • Printf%s, %d, %f

  • iOS%@, %li, %f

  • ICU{name}, {0}

  • .NET{0}, {1:0.00}

  • Symfony%name%, %placeholder_1%

  • i18n{{name}}, {{0}}

  • Raw — no changes

Recommendations

Use named parameters

✅ "Price: [%.2f:price]$"
❌ "Price: [%.2f]$"

Plan types in advance

  • String for names and text

  • Integer for counters

  • Float for prices and percentages

Test the result

Check export on all target platforms.

Troubleshooting

Placeholders don't convert during import
Enable "Convert to universal placeholders" option during upload.

Wrong format after export
Check the selected placeholder format in export settings.

Loss of named parameters
Make sure the target format supports named parameters.