A tool that allows you to edit the translations from your application at run-time, so you can immediately see and share the changes.

The process of writing copy for a mobile applications can be quite complicated, especially when it is done by a copywriter:

Provide them with access to the repository of your application? But, then the copywriter needs to be at least tech savvy, and you have to be willing to share your code.

Or do you give him a working application and a list of Strings? In that case, the copywriter can't see the effect of the changes they make.

We decided to develop a library that will simplify the whole process: we provide the copywriters with a version of the application tailered for them.

All they have to do now is to shake their phone, and all Strings from the current screen will be displayed in a list. They can be edited, applied by the press of a button, and the new strings will be immediately visible in the app.

And once the translation is done all the changes can be shared from the application in a format defined by the developer.


Setup

Let's now go through the steps for adding this library to your own apps.
Before we begin, just a notice: we're using 2 libraries here. kunstmaan-shaker-menu is a utility library that allows us to show a popup menu after shaking your phone, and kunstmaan-translations-editor is the actual translations tool.

Begin by adding the libraries as gradle dependencies. Our recommended approach is to only use the library in either debug or alpha builds, and not in beta or production.
 

alphaImplementation 'be.kunstmaan.android:kunstmaan-shaker-menu:1.0.3'
alphaImplementation 'be.kunstmaan.android:kunstmaan-translations-editor:1.0.0'

Define the strings.xml files that we want to use in the library.
Let's say we have a default strings.xml (without any modifier) used by our default locale (en_US) and a file for Dutch and French Locales with the same strings as the default one but untranslated (strings.xml (fr)   strings.xml (nl) ).

List<Locale> localeList = new ArrayList<>();
localeList.add(new Locale("nl"));
localeList.add(new Locale("fr"));
localeList.add(new Locale("en", "US"));

Then we build our library with those Locales,.
We also need to specify what is our default Locale and provide the fields from R.string and the Application context.

new KunstmaanTranslationUtil.Builder(application, R.string.class.getFields(), localeList, new Locale("en", "US"))
                             .build();

Once the Translations library is setup, we'll initialize our shake detection popup menu that can start the Translations tool from any activity.

new KunstmaanShakerMenu.Builder(application)
                .setTitle("How can I help you ?")
                .setSensitivity(KunstmaanShakerMenu.Sensitivity.LIGHT)
                .addItem("Show translations", new Runnable() {
                    @Override
                    public void run() {
                        KunstmaanTranslationUtil.showTranslationsWindow();
                    }
                })
                .build();

Usage

At this point, everything should be good to go and we can start translating!

Shake your phone on the screen to be translated and choose the popup menu item we setup earlier.

We can see the Strings that are present on the screen we came from. Let's edit them.

The strings are divided into 3 categories :

  • Current : Strings from the current view
  • In memory : Strings present in memory
  • All : All Strings of the application

          

We also can see and edit the version of this string in ohter Locales

Now we want to see the changes in the application, we just have to press the "Apply Changes" button, and the app will restart with your translation changes applied.

To share the translation changes, you can use the "share" option in the overflow menu.


Additional features

There are a few extra features provided in our tool:
 

Add a pattern that the library will ignore.

Let's say you are using a library you use to debug or simply you don't want some of your strings to appear in the translations list. You can use a regex to specify which keys from strings.xml should be ignored.

E.g.: this regex that compiles to “all words beginning with to_be_ignored”

.addIgnorePattern(Pattern.compile("^to_be_ignored*$"))

makes that this string (from strings.xml) will not be shown in the translations list (no matter if it is on the screen or not).

<string name="to_be_ignored_or_not_to_be_ignored">This is the question</string>

Set a custom format for the file that will be shared.

The Library provides two default formats for the Strings you share, xml and json :

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<Translations>
<translation>
    <locale>locale</locale>
    <key>key_of_the_string</key>
    <oldValue>old value</oldValue>
    <newValue>new value</newValue>
</translation>
</Translations>
[
  {
    "key": "key_of_string",
    "locale": "localeOfTheString",
    "newValue": "new value",
    "oldValue": "old value"
  }
]

If this defaults doesn't suit your needs, you can specify your own.
Use this placeholders in a string and they will be replaced with the value they represent :

  • ${key}
  • ${locale}
  • ${newValue}
  • ${oldValue}
new KunstmaanTranslationUtil.Builder(application, R.string.class.getFields(), localeList)
    .addCustomJsonFormat("{\n" +
                        "  \"myCustomNameForTheKey\": \"${key}\",\n" +
                        "  \"myCustomNameForTheNewValue\": \"${newValue}\",\n" +
                        "}")

will give this output, when shared in json :
 

[
  {
    "myCustomNameForTheKey": "key_of_the_string",
    "myCustomNameForTheNewValue": "new value",
  }
]

Search for strings in the list.

On each screen (Current, In memory, All) you can search for strings.

The search will be performed on the keys and values.

Filter changed/unchanged values.

I you want to see only the values that are changed or unchanged, the overflow menu has buttons to filter those values.


The translations library and shaking detection library can be found on GitHub with working examples:

Written by

Michal Borkowski

Follow @borkowmichal