Localize your plug-in

Tutorial
by Rainer Erich Scheichelbauer
en zh

29 July 2022 Published on 20 November 2018

So you have written that great plug-in, and it works as expected, and your users are happy. But then the first complaints are in: ‘Que? No español?’ or ‘Qu’est-ce que ça veut dire? Pas de Français?’ or even ‘Jahimmelherrgottkruzifix, gibt’s denn koa Boarisch?’

Your plug-in does not speak German, French and Spanish yet? Well, here is how to fix it. (Warning: advanced stuff in this tutorial.)

In short, your plug-in only speaks English, but your users are running the app in other languages. If you do not speak their language, usually they will be very cooperative when you ask them how they would translate the interface in question. Or perhaps you meet someone you can ask. What you should not do, is simply run the text through Google Translate, or you may end up with some very confused users. You have been warned.

Step 1 Put your .nib and .xib into Base.lproj

In order for the translation to work, we have to put the files that need translation into a special subfolder. This collection of original untranslated files is referred to as the Base. Hence, the subfolder will be called Base.lproj, because it is the Base of our language project.

Okay, here we go. Go into your plug-in by right-clicking it and choosing Show Package Contents from the context menu. From there, navigate to Contents > Resources. You should find your plugin.py, your IBdialog.nib and IBdialog.xib there. In case you made a more sophisticated UI, there will be more .nib and .xib files.

  1. Determine the Base. Select all .nib and .xib files:
  1. Collect the Base in a subfolder. Right-click and choose New Folder with Selection from the top of the context menu that pops up:
  1. Name the Base subfolder. Rename the newly-created folder to Base.lproj:

Pay attention: the name is case-sensitive, so uppercase B, and the rest is lowercase.

The Base.lproj folder is the basis, and now we will add language-specific .lproj folders that contain the translations for the Base. But first, we will create the English .lproj. Yes, you read right, English.

Step 2 Add english .lproj folder

Why do we need an English language project folder? After all, the plug-in already is in English. So one would think you would need only the translations anymore. Well, not quite. The point is that the Base is not supposed to change anymore, or as little as possible, because what you will want to avoid at all cost is that the IDs change. (You will read about IDs further on in this tutorial.) That means if you change the English text at a later point, e.g., because you want to correct a typo, you will do that in the .lproj file, not in the .xib/.nib anymore.

  1. Create the language folder: Next to the Base.lproj folder you just created, add one folder called en.lproj:
  1. Add .strings file for each .nib file: Then fire up your favourite text editor, like Atom, SublimeText or TextMate, create a new file and save it inside the en.lproj folder with the name IBdialog.strings:

The name must be the same as the .nib file in Base.lproj. Again, the name is case-sensitive, uppercase IB, lowercase dialog, and the .strings suffix.

If you have other .xib/.nib files besides IBdialog, please add corresponding .strings files, one for each .nib.

That’s it in step 2. If you have done everything right, it will look approximately like this:

So far, so good. But an empty .strings file is of not much use. Let’s add some content. So do not close the file yet.

Step 3 Populate english .strings file

What we need to do now is tell the plug-in that certain text elements get new text content. In order to do that, we have to identify the element with an ID, and assign one of its attributes, typically the title, a new text string.

  1. Finding the ID: Open the .xib file in Base.lproj with Xcode and select the text cells that need translation. Attention: Make sure you actually select the text field cell, not just the text field. This is important because field and cell have different IDs. I.e., open the triangle symbols in the Dialog sidebar on the left side. You can also see it by the way the element is highlighted, see the screenshot below.

Then, with the text cell still selected, choose the Identity Inspector in the right sidebar (the third icon there). Now look for the Document pane in the sidebar, and select and copy the Object ID. Paste the ID in your .strings file. It is a good idea to add a description, or the original English text, in a comment between /* and */, so you know what the ID refers to without opening the .xib file again. So, for now, it will look like this in your .strings file:

/* "Offset" */
STH-Ua-fbl

Do the same for all other text cells in the dialog. If you have placeholders or tool tips associated with the Object ID, consider adding those as well.

  1. Find the attribute: The Object ID refers to the object, but what we want to do is assign new values to some of its attributes in the context of different language settings. In plain English, we want to translate (=assign new values to) one or more of the text strings (=attributes) attached to the text field cell (=object).

In order to determine the proper keyword for your attribute, take a look in the Identity Inspector (3rd icon above the right sidebar) or Attributes Inspector (4th icon), and transform the names you see there into camelcase (starting with a lowercase letter), and you have the keywords of your attributes. E.g., Tool Tip becomes toolTip, Title becomes title, Placeholer becomes placeholder.

You can see there are many other attributes there as well, and you can access them the same way. But usually, for a translation, only the ones that carry text will make sense to include in your .strings file.

  1. Clean up your .strings code. The content of the .strings file must be in C notation, so our code will have to be transformed like this:
/* "Offset" */
"STH-Ua-fbl.title" = "Offset";
  1. Add the attribute as a dot suffix to the ID: STH-Ua-fbl.title
  2. Wrap the suffixed ID in dumb quotes: "STH-Ua-fbl.title"
  3. Append an equals sign: =
  4. Append the assigned text between dumb quotes: "Offset"
  5. At the end of the line, add a semicolon: ;
  6. Double check if your comment is between /* and */

Do this for all attributes you want to translate. If you have done everything right, your text editor will help you with syntax coloring, and it will look something like this:

Again, an object may have more than one attribute you want to translate. Our example might just as well look like this:

/* "Offset" */
"STH-Ua-fbl.title" = "Offset";
"STH-Ua-fbl.toolTip" = "Width of the shape contour";

That is it for step 3. Keep in mind that, in the future, you should make your changes in this .strings file, not in the .xib anymore.

Step 4 Create non-english .lproj folders

Now that we have the Base and the English language project set up, we need a language project folder for each language we want to support. They are exactly the same as the en.lproj folder we already created, except that, in our .strings file, we will add the translation for the respective language.

Okay, here we go:

  1. Create the language-specific language project folders. In Finder, select the en.lproj folder and duplicate it (File > Duplicate, Cmd-D), then rename the new folder to the two-letter ISO 639-1 language code followed by the .lproj suffix:
  1. Add the translations. Inside each .lproj, open the .strings files and change the text between dumb quotes on the right side of the equals sign.

Some advice: If you do not speak the language, consider sending the .lproj folder to a user of your plug-in who does and ask them to translate the strings for you. If you do not know anyone, consider a forum post requesting help. For plug-ins, there is usually very little text to translate and it is easy to find someone who will do the favour for free, especially if the plug-in you offer is free as well. If it is commercial, consider offering a free copy of your plug-in in return.

You can add any language of course. First and foremost however, consider the languages that Glyphs.app is localized for already. That way, you can enable a smoother UI experience for users in these languages:

  • cs: Czech
  • de: German
  • en: English
  • es: Spanish
  • fr: French
  • it: Italian
  • ja: Japanese
  • ko: Korean
  • pt: Portuguese
  • ru: Russian
  • tr: Turkish
  • zh_CN: Chinese (simplified, mainland China)
  • zh-Hant: Chinese (traditional, Taiwan)

You can see that Chinese appears twice in the list, with two different suffixes. That is because it needs to be differentiated into its two possible script variants: simplified and traditional. The former is written in mainland China, the latter on Taiwan/ROC.

Step 5 Re-compile the .xib once

One more thing remains to be done. Since we transformed our plug-in into a localized project differentiating between Base and language-specific customisations, we need to compile the .xib to a .nib again.

You probably know the drill: Open the Base.lproj folder in Terminal.app, type ibtool IBdialog.xib --compile IBdialog.nib and press the Return key. Or, better yet, drag the .xib file on the Compile .xib to .nib.app provided for your convenience in the GlyphsSDK repository.

The good news: You only need to do this once. All future additions and changes to the translations only need to be specified in the .strings files, and you’re good. High five!

Step 6 Localize texts in your Python code

Did we forget something? Yes we did. If you wrote your plug-in in Python, there probably are some strings in your .py file that need to be translated as well: typically the menu name, probably the word on the button, and perhaps als the context menu if your plug-in has one.

In this case, we can keep it inside the .py file. Simply replace all your strings (including Unicode strings) with a Glyphs.localize() function. The localize() function takes a dict as an argument, where the dict keys are the two-letter language codes, and the corresponding values the translations, preferably as Unicode strings.

To give you an example, you would turn this:

self.menuName = 'Shadow'

… into this:

self.menuName = Glyphs.localize({
    'en': u'Shadow',
    'de': u'Schatten',
    'fr': u'Ombreur',
    'nl': u'Schaduw',
    'es': u'Sombrear',
})

Note the u in front of the strings. It will turn the string in a Unicode string. You need this if the translation contains non-ASCII characters. It does not hurt to stay on the safe side, and always add them, though. If you use Unicode strings in your .py file, it may be a good idea to declare the encoding at the beginning of your file, like this:

# encoding: utf-8

By the way, this also works with all your regular Python scripts outside the realm of plug-ins. It even integrates nicely with vanilla code. Pretty cool.

OK, I think that’s it. You now have all the tools for localizing your plug-ins in your hands. Once you get the hang of it, it is some kind of geek fun and you will find it hard to stop. So, feel free to go localize your hearts out. :-)

Test the localization

In order to test the localization, first make sure you unchecked Glyphs > Preferences > User Settings > Disable Localization. Otherwise you will not be able to switch Glyphs into a different language.

Then go to System Preferences > Language & Region, and drag the languages you want to test on top of the list. If the language does not show up, add it with the plus button. For instance, to test the Spanish localization, add Spanish to the list, and drag it on top, like so:

To confirm your changes, simply close System Preferences again. I dialog will ask you if you want restart your Mac. You do not want to restart your Mac.

You do want to restart Glyphs.app, though.

After startup, Glyphs is running in Spanish! Look for your plug-in under the Filtros or Vista menus. Its name should have changed to whatever you españolified self.menuName to. And voilà:

Troubleshooting

Okay, I cannot lie to you, there is always something that will go wrong. If the translation does not show up and you are lost along the way, these few tips will help you spot the problem quickly:

  • Not the right object: Did you make sure you are referencing the correct object with the ID you found out? Most typically, you accidentally used the ID that points to the text field, not the text field cell. Make sure that you drill down far enough in the Dialog section of the Xcode sidebar. Look here, the one with the loving smiley is the one you want:
  • ID has changed: Did you make changes to the .xib? Perhaps replaced one of the text fields with a new object or pasted one from another .xib in? Chances are the IDs have changed. You will need to both recompile the .xib and especially verify the Object IDs again.
  • Forgot the semicolon: In the .strings file, after every assignment, you must put in a semicolon. Cannot spare you that one, I am sorry.
  • Recompiled once? Did you recompile the .xib after you added Base.lproj? It does not cost anything to do the compilation once more, so you may want to do that anyway, just to be on the safe side.


Update 2020-11-13: reformatting for Glyphs 3 website (tips and teaser paragraph).
Update 2022-07-29: updated title, related articles, minor formatting.