Localazy Android library

The Localazy Android library translates the app on the fly, updates translations, collects and sends anonymous statistics and provides additional methods for controlling how your app is being translated.

If you followed instructions on how to add Localazy into your app, there is nothing else you need to do to have your app translated. However, the Localazy library provides additional methods described in this documentation that you can use to improve users’ experience and get more volunteers.

Compatibility

  • the library is extremely lightweight and has no dependencies by itself
  • Android from API level 14 is supported
  • Kotlin projects are fully supported

Issues reporting

Unfortunately, Android doesn’t provide a unified way to override how it obtains texts and translations on the fly. If you notice that something is not correctly translated, please contact us at info@localazy.com.

Integration and initialization

For integration and initialization of the library, please follow instructions on how to add Localazy into your app.

We don’t recommend initializing the library manually as the Gradle plugin modifies the bytecode of your app on several different places. Without the changes made, Localazy wouldn’t be able to fully translate your app. Manual initialization could cause undesired situations and incomplete translations.

Typically, the library is initialized by the first call to attachBaseContext method inside Application or your first Activity / Service. The initialization code is injected into these places by the Localazy Gradle plugin.

Enabling and disabling

You can temporarily enable/disable on-the-fly translations and stats collecting (see Collecting stats).

Localazy.setEnabled(false);
// Localazy is disabled, and translations are obtained by the default Android mechanism.

Localazy.setEnabled(true);
// Localazy is enabled again and provides translations.

// We can also check for the current state.
if (Localazy.isEnabled()) {
    // The library is doing its hard work...
}

Localazy.setStatsEnabled(false);
// Collecting and sending stats data is completely disabled.

Localazy.setStatsEnabled(true);
// We are collecting data to improve your translations again...

// And we can check for the current state.
if (Localazy.isStatsEnabled()) {
    // The library is making your app better.
}

Changes to setEnabled or setStatsEnabled are not stored persistently. On-the-fly translations and stats collecting are re-enabled when the app is restarted. You can introduce your logic for persisting the state.

Project information

Knowing the URL of your app on the Localazy site is useful as you can ask people to help you with translating the app and send them directly to the translation platform.

You can get Uri pointing to your app with this code:

Uri projectUri = Localazy.getProjectUri();

Method getProjectUri() returns null if the information is not available (Localazy is not initialized yet, data are not correctly loaded, etc.).

Working with locales

Localazy doesn’t change the locale of your app. Internally, it resolves the closest locale for which it has a translation available and uses it.

To obtain the current internally resolved locale use:

// As Android's Locale.
Locale currentLocale = Localazy.getCurrentLocale();

// As LocalazyLocale that provide more information. 
LocalazyLocale currentLLocale = Localazy.getCurrentLocalazyLocale();

The call to getCurrentLocale() or getCurrentLocalazyLocale() returns null if the information is not yet available (Localazy is not initialized yet, data are not correctly loaded, etc.).

To get information about whether the current internally resolved locale is fully translated use:

boolean translated = Localazy.isFullyTranslated();

The call to isFullyTranslated() returns false if Localazy is not initialized yet, data are not correctly loaded, etc.

To list all locales know to Localazy:

List<LocalazyLocale> locales = Localazy.getLocales();

The call to getLocales() returns null if the informatison is not yet available (Localazy is not initialized yet, data are not correctly loaded, etc.).

To force the library to switch the app locale to another one:

// The second parameter allows making the change persistent.
Localazy.forceLocale(newLocale, persistent);

// Remove previously set forced locale.
// A restart of the app (opened activities) may be necessary.
Localazy.forceLocale(null, persistent);

Forcing locale affects not only on-the-fly translations but also system texts, resources, etc. It’s like switching the whole app into another language.

This method may be invoked even if the library is not initialized yet, and forced locale is correctly applied later. Before the initialization finishes, it doesn’t affect your app locale.

This method doesn’t change the existing content and already inflated views. You have to take care of refreshing content.

To invalidate internal caches and reload data (useful for multi-process apps or handling external events):

Localazy.forceReload();

This method doesn’t change the existing content and already inflated views. You have to take care of refreshing content.

LocalazyLocale

The list of all known locales returned by Localazy.getLocales() is represented by a list of LocalazyLocale instances.

LocalazyLocale locale;
// ...

// Returns language as ISO 639 code (eq. zh).
String language = locale.getLanguage();

// Returns country/region as ISO 3166 code (eq. TW).
// Returns null if the country/region is not contained.
String region = locale.getCountry();

// Returns script as ISO 15924 code (eq. Hant).
// Returns null if the script is not contained.
String script = locale.getScript();

// Returns the locale as java.util.Locale.
// This method considers the script on Android 5+.
Locale l = locale.getLocale();

// Returns true if the locale is completely translated.
boolean translated = locale.isFullyTranslated();

// Returns display name for the locale in English - eq. "Czech (Czechia)" for "cs_CZ".
String name = locale.getName();

// Returns display name for the locale in its own language - eq. "Čeština (Česko)" for "cs_CZ".
String localizedName = locale.getLocalizedName();

// Returns the internal Localazy ID for this locale.
// It's useful for constructing URL addresses of the Localazy platform.
short localazyId = locale.getLocalazyId();

You can use information from locale.isFullyTranslated() to ask people to help you with translating the app into languages that are not fully translated yet.

Obtaining strings

There may be some situations where you need to obtain translated strings on the places where the library couldn’t be injected by the Localazy Gradle plugin (such as when writing custom view transformers).

Localazy singleton offers a variant of all Android’s standard methods for obtaining strings, string arrays and plurals that are guaranteed to return translated version if available.

// The same as Resources.getString(int)
Localazy.getString(context, R.string.identifier);

// The same as Resources.getString(int, Object...)
Localazy.getString(context, R.string.identifier, params);

// The same as Resources.getText(int)
Localazy.getText(context, R.string.identifier);

// The same as Resources.getText(int, CharSequence)
Localazy.getText(context, R.string.identifier. defaultValue);

// The same as Resources.getTextArray(int)
Localazy.getTextArray(context, R.array.identifier);

// The same as Resources.getStringArray(int)
Localazy.getStringArray(context, R.array.identifier);

// The same as Resources.getQuantityText(int, int)
Localazy.getQuantityText(context, R.plural.identifier, quantity);

// The same as Resources.getQuantityText(int, int, Object...)
Localazy.getQuantityString(context, R.plural.identifier, quantity, params);

// The same as Resources.getQuantityText(int, int)
Localazy.getQuantityString(context, R.plural.identifier, quantity);

If the library is not initialized or is disabled or when the translation is not available, methods above fallback to the standard Android mechanism. So, it’s completely safe to use them.

Listening to events

You can register LocalazyListener using Localazy.setListener(…​) and listen to several events emitted by the library.

Method Description
missingTextFound(LocalazyId id, Locale locale, String key) The key is not translated into the current language.
missingKeyFound(Locale locale, String key) The key is unknown to Localazy, which probably means that you don’t upload updated texts, or the key was excluded during strings uploading.
stringsUpdateStarted() The library has started the update process.
stringsUpdateFinished() The library has updated the translation data.
stringsUpdateFailed(int errorCode) The data update process failed with an error identified by errorCode. See error codes for more information.
stringsUpdateNotNecessary() The library contacted the update server and found out that there is no change.
stringsLoaded(boolean fromUpdate, boolean success) The library has loaded data - this is called after initial load, strings update, etc.

Events are not emitted on the main UI thread!

LocalazyId

If there is a missing translation detected, you receive LocalazyId that allows you to ask people for help with translating it.

public void missingTextFound(LocalazyId id, Locale locale, String key) {

    // Get the direct URL for translating the phrase.
    String url = id.getPhraseUrl();

    // Get hidden state of the phrase.
    boolean hidden = id.isHidden();

}

If the phrase is hidden on the platform, don’t ask people for help. There are reasons why it doesn’t need a translation.

Error codes

Error code returned by stringsUpdateFailed(int errorCode) can be:

Code Description
400 Invalid request.
401 Invalid authorization. Please check the read key.
403 Invalid app package or certificate. Be sure that the given combination has been allowed in your project’s settings.
404 The update package cannot be found. It usually means that you haven’t uploaded and published your translations yet.
409 The update package cannot be downloaded because you hit account limits; payment has to be done.
426 The update package cannot be downloaded because the library contained in your app is too old. Please update the library.
429 Too many request to the server.

Testing on-the-fly functionality

You can enable the prefix feature that adds [LL] prefix to strings that comes from Localazy on-the-fly translating engine.

This is useful for testing that all parts of your app are correctly translated with Localazy and also for testing Custom view transformer.

You can enable this feature in the Localazy Gradle plugin configuration with addPrefix option or in the code:

Localazy.addLocalazyPrefix(true);

Due to intensive internal caching, enabling and disabling the feature on the code level needs the app to be restarted in order to invalide all internal caches. You have to handle the restart on your own.

StringsLoaded annotation

If you don’t want to implement the full LocalazyListener to receive information about changes to strings, you can use @StringsLoaded annotation.

Listening to this event is useful if you want to update your UI when new translations are available.

In your Activity (or any other Context-based class) add a method without parameters:

@StringsLoaded
public void stringsLoaded() {
    // update your UI
}

or a method with two boolean parameters that provide additional information about whether the strings were updated after the new version has been downloaded from Localazy servers and whether the loading of data has succeeded:

@StringsLoaded
public void stringsLoaded(boolean fromUpdate, boolean success) {
    // update your UI
}

Please don’t use this annotation multiple times in the same class. It’s registered only once.

The call to this method isn’t made on the main UI thread. Be sure to perform all changes to your UI on the main thread.

Custom view transformer

As Android doesn’t use standard way for obtaining strings while inflating layouts, it’s necessary to use view transformers that update the view after it’s inflated.

The library contains transformers for all standard views, including those from support and AndroidX libraries.

The most probably, your custom components are derived from system ones like TextView and so you don’t need to do anything special for them to be supported correctly by Localaly on-the-fly translations.

If your component is not correctly translated with Localazy, you can write your own tranformer. You just need to implement 3 methods. See the simple example code above.

class MyTransformer implements LocalazyViewTransformer {

    private static final String ATTRIBUTE_TEXT = "myInternalText";

    @Override
    public boolean shouldRegister() {
        // You can skip transformer registration if conditions are not met.
        // This mainly useful in libraries that may be applied to different
        // projects.
        try {
            Class.forName("my.flavor.MyView");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean isSupportedType(Object any) {
        // At this point, it's decided whether to use the registered transformer
        // for the given view type.
        return any instanceof my.flavor.MyView;
    }

    @Override
    public View transform(Context current, View view, AttributeSet attrs) {
        // Find attribute with text and do necessary transformations.
        for (int index = 0; index < attrs.getAttributeCount(); index++) {
            String attributeName = attrs.getAttributeName(index);
            if (ATTRIBUTE_TEXT.equals(attributeName)) {
                String value = attrs.getAttributeValue(index);
                if (value != null && value.startsWith("@")) {
                    // Use Localazy singleton to obtain strings as 
                    // current context may not be Localazy-enabled by itself.
                    int resValue = attrs.getAttributeResourceValue(index, 0);
                    String text = Localazy.getText(current, resValue);
                    ((my.flavor.MyView)view).setMyInternalText(text);
                }
            }
        }
        return view;
    }

}

Developer app

The library uses an internal cache mechanism which may not be desired when testing translations.

Using our Localazy Developer app, you can force your app to update strings from the server and reload them.

Download the app from the Play Store: Localazy Developer app

Collecting stats

The library collects anonymous data to improve translations and suggest optimization tips. It’s important to prioritize translations to maximize value for users.

Collected data:

  • detected and resolved locale
  • phrase usage
  • Android version
  • library version
  • generated user ID - not stored for more than 24 hours

The library don’t collect personal data, unique identifiers other than generated user ID, etc.

When uploading stats to our servers, the IP address may be temporarily stored. Our lawful basis for processing the IP address is the legitimate interests to protect our servers and technical infrastructure.

Join Localazy today

Translating apps has never been easier. Try Localazy for free.

Translate your app