Skip to content

How to write localizable code

nofish edited this page Dec 15, 2021 · 1 revision

Any string that is displayed to the end user must not be hard coded but "localized" (so that it can be translated). Cockos kindly shared its localization code with us as well as detailed instructions, see below. Thank you Cockos!


Here is the localization stuff. To use, from one module, you should have something like:

#define LOCALIZE_IMPORT_PREFIX "sws_"
#include "../localize-import.h"
#include "../localize.h"

And from all of the others, just:

#include "../localize.h"

Note that it will require localize-import being included before localize.h in that one module, you can sort out how to make that happen... for reaper_midi, we have a main.h which has:

#ifdef LOCALIZE_IMPORT_PREFIX
#include "../localize-import.h"
#endif
#include "../localize.h"

and then all modules include main.h, and one has a #define LOCALIZE_IMPORT_PREFIX at the top... Anyway, once that is done, we call:

IMPORT_LOCALIZE_RPLUG(rec)

in the plugin entry point. Once that is done, you can do the following things to localize strings:

__LOCALIZE("Whatever english text","sectionname")

str.AppendFormatted(1024,__LOCALIZE_VERFMT("There are %d REAPER users",sectionname"),8); // verifies the translated string's format specifiers against the original

if you have a table of strings, you can surround it with special comments:

// !WANT_LOCALIZE_STRINGS_BEGIN:sectionname
"some string",
"some other string"
// !WANT_LOCALIZE_STRINGS_END

then, when accessing it, you should translate using:

__localizeFunc(stringptr,"sectionname",0)

Note that __LOCALIZE() are special and require string literals (they are parsed by the build_sample_langpack tool).*

There is also a helper function, localizeKbdSection(), in localize-import.h...

I realized now I already wrote a guide for some of this here it is:


If you will be adding a string that is to be localizable, you do it via this macro:

__LOCALIZE("my string","section");

Note that both parameters to the macro MUST be literal strings, and be one block of string (i.e. not using concatenation, i.e. "part1" "part2" etc). If you are calling from code that could possibly be in a thread other than the main thread, use:

__LOCALIZE_NOCACHE("my string","section");

The section name depends on the context, some common sections that are used: "undo" -- undo point names "mbox" -- message box prompts "splash" -- messages that go to the splash screen

If you would like to have a format specifier in the string, you can use:

sprintf(buf,__LOCALIZE_VERFMT("This has %d items","section"),6);

However, you should make sure that the buffer used is more than large enough to hold an exceedingly long translation in this instance. It is probably better to avoid this situation and just use something like:

char buf[256];
sprintf(buf,"%.200s: %d",__LOCALIZE("Item count","section"),6);

The value returned by __LOCALIZE/etc is effectively a const char *, and will persist, so it is safe to pass whereever and use again. If you are really performance sensitive (i.e. used in the arrange redraw, meter redraw, listview populating, etc), you might want to do:

static const char *msg;
if (!msg) msg = __LOCALIZE_NOCACHE("whatever","section");

This will do the lookup once, and cache the result.

If you have strings which are present in a table such as:

struct foo bar[]={
  {x,y,z,"string 1"},
  {x,y,z,"string 2"},
  {x,y,z,"string 3"},
 };

The best way to handle this is to put comments around the string table, such as:

// !WANT_LOCALIZE_STRINGS_BEGIN:section_name
struct foo bar[]={
  {x,y,z,"string 1"},
  {x,y,z,"string 2"},
  {x,y,z,"string 3"},
 };
// !WANT_LOCALIZE_STRINGS_END

Then, supposing you reference these strings via bar[x].stringptr, you would use:

__localizeFunc(bar[x].stringptr,flags)

(where flags can be 0, or LOCALIZE_FLAG_VERIFY_FMTS or LOCALIZE_FLAG_NOCACHE or some combination of those, see localize.h)

There currently a limit of around 8k for localized strings -- if you are localizing a huge block of text, it might be good to divide it up into separate strings. Finally, for resources, the menus and dialogs are localized automatically via a wrapper function and some #defines, which are in localize.h

Slight updates for the SWS extension

In comparison with these instructions, I have just added a little thing (development_root/sws/GenLangPack/sws_build_sample_langpack.cpp) to easily register COMMAND_T tables: we have additional tags WANT_LOCALIZE_1ST_*. Notes: it avoids to fill the LangPack file with useless custom IDs (and users must not be able to translate them!). Also, COMMAND_T.menuText are not used anymore (well, apart for some color tools).

If you have strings which are present in a table such as:

struct foo bar[]={
  {x,y,z,"string 1","string 11"},
  {x,y,z,"string 2","string 22"},
  {x,y,z,"string 3","string 33"},
 };

And you only want to offer translation for "string 1", "string 2" and "string 3" (but not "string 11", etc..), the best way to handle this is to put comments around the string table, such as:

//!WANT_LOCALIZE_1ST_STRING_BEGIN:section_name
struct foo bar[]={
  {x,y,z,"string 1","string 11"},
  {x,y,z,"string 2","string 22"},
  {x,y,z,"string 3","string 33"},
 };
//!WANT_LOCALIZE_1ST_STRING_END

Then, supposing you reference these strings via bar[x].stringptr, you would use:

__localizeFunc(bar[x].stringptr,section,flags)

(where flags can be 0, or LOCALIZE_FLAG_VERIFY_FMTS or LOCALIZE_FLAG_NOCACHE or some combination of those, see localize.h)

Note: WANT_LOCALIZE_1ST_ also ignores strings containing "[Internal]" (i.e. do not localize internal/test actions)*

How to ease the job for translaters?

  • When your strings are tied to a dialog box (defined in sws_extension.rc), it is a good idea to use the same section name "sws_DLG_xxx", where xxx is the IDD of the dialog box
  • For simple message boxes (not attached to a dialog box defined in sws_extension.rc), use the section name "sws_mbox".
  • For undo points, use the section name "sws_undo" and the macro SWS_CMD_SHORTNAME() for the undo point name itself (job/=2 for translators: translating the action name will be enough).
  • For menus and context menus (not attached to a dialog box defined in sws_extension.rc): "sws_menu".
  • For the main extension menu: "sws_ext_menu".

These section names are also consistent with REAPER ones.

How to generate the SWS template LangPack file?

The the SWS template LangPack file is automated via a script (development_root/sws/GenLangPack/sws_build_template_langpack.sh). This script requires the project source tree to be named "sws", i.e. development_root/sws/. It relies on a tiny sub-project (see development_root/sws/GenLangPack/, Win32 only ATM) that you need to build first. This project is made of one file: the build_sample_langpack.cpp discussed above.
Note: this script is itself automatically performed at release time (see Install/build.bat).

Also see http://forum.cockos.com/showpost.php?p=941893&postcount=810

When do I need to re-generate the SWS template LangPack file?

When at least one of the following items changes:

  • Resources, i.e. changes in development_root/sws/sws_extension.rc
  • Content of the main menu > extensions, i.e. changes in development_root/sws/Menus.cpp
  • New/updated action names (the latter should be avoided since existing translations will be broken!)
  • New source files using __LOCALIZE*()
    Note: in this case do not forget to update/commit the script "sws_build_template_langpack.sh"!