How to localize an addon
This tutorial will walk you through adding localization to your addon.

What is localization?

Localization is the process of making sure that your addon works in all locales (regions of the world where different languages are used), and that people who dont speak your language can still understand and use your addon.

Sometimes localization is required for an addon to even work in another locale. For example, if youve written an addon to play a sound when you mouse over a Peacebloom, your addon needs to know what text is shown in the tooltip in other languages. The tooltip shows Peacebloom in your English game client, but it shows a different word or phrase in a Spanish game client, or a Chinese one. If your addon only looks for the word Peacebloom, it wont work in other languages. By localizing your addon, you can make it look for the right word or phrase for the language in which its user is playing WoW.

Other times, your addon might work fine in other locales, but still isnt very usable, because all the text is in English and someone who only speaks French cant understand it. For example, if your addon has an options window, localization would mean adding translations of the text shown for those options, so that German and Russian users can understand what the options do just as easily as English users.

If you simply want to copy and paste some code, feel free to do that. If you want to learn more about how it works, read on after the code for details.

Step 1: Create a translation table

Add a file to your addon named Localization.lua", containing this:

local _, namespace = ...

local L = setmetatable({}, { __index = function(t, k)
	local v = tostring(k)
	rawset(t, k, v)
	return v
end })

namespace.L = L

local LOCALE = GetLocale()

if LOCALE:match("^en") then
return end

if LOCALE == "deDE" then
	-- German translations go here
	L["Hello!"] = "Hallo!"
return end

if LOCALE == "frFR" then
	-- French translations go here
	L["Hello!"] = "Bonjour!"
return end

if LOCALE == "esES" or LOCALE == "esMX" then
	-- Spanish translations go here
	L["Hello!"] = "Hola!"
return end

if LOCALE == "ptBR" then
	-- Brazilian Portuguese translations go here
	L["Hello!"] = "Ol!"
return end

if LOCALE == "ruRU" then
	-- Russian translations go here
	L["Hello!"] = "Привет!"
return end

if LOCALE == "koKR" then
	-- Korean translations go here
	L["Hello!"] = "안녕하세요!"
return end

if LOCALE == "zhCN" then
	-- Simplified Chinese translations go here
	L["Hello!"] = "您好!"
return end

if LOCALE == "zhTW" then
	-- Traditional Chinese translations go here
	L["Hello!"] = "您好!"
return end
Be sure to save this file with UTF8 encoding, so that non-Latin characters (such as those used in Russian, Korean, and Chinese, and even the accented letters in French, Spanish, and Portuguese) are preserved properly.

Add the path to this file in your addons TOC file, above any other files where localization will be needed.

Step 2: Localize text using the translation table

First, get a reference to the translation table at the top of each file where you will need to use it:

local ADDON_NAME, namespace = ...
local L = namespace.L
Then, anywhere you would show a string of text to the user:

local text = string.format("You have %d cats and 1 %s.", 5, "dog")
Look up the string in your translation table and show the resulting value instead:

local text = string.format(L["You have %d cats and 1 %s."], 5, L["dog"])
Step 3: Getting translations

Often, if you simply add a note to your addons download page indicating that youre looking for translations, and for which languages, people who use your addon and speak one of those languages will happily supply you with translations.

There are also people who may not use your addon personally, but are active in a language-specific WoW community. For example, there is a large Chinese WoW community with many people who just like translating addons so that other Chinese players can use them.

Simple words and phrases can often be translated with a machine-translation service like Google Translate, or be constructed by looking at the Blizzard global strings for other locales. (Though, if the complete word or phrase you want to use already exists as a global string, you should just use the global string in your addon, instead of copying and pasting the translated strings into your translation table.)

If you use SVN for your addon, keeping the repository open and including the repository URL along with your requests for translations may yield better results, as translators can simply commit their translations without anyone having to copy and paste code through PMs or forum posts.

If your addon is hosted on CurseForge/WowAce, you can also use their localization app to provide a web-based method for accepting translations. If you are using a CurseForge/WowAce repository for your addon code, you can automatically pull in new translations. Otherwise, you can easily export new translations and copy/paste them into your addon.

How it works

The core of this simple localization solution is a metatable, a special feature of Lua tables that allows you to specify custom behaviors on the table.

In this case, we are using a metatable index so that when we look up a key in the translation table that doesnt exist, instead of returning a nil value, a custom function runs that transforms the given key name into a string, adds the given key name to the translation table with the string as its value, and then returns the string.

The beauty of a metatable is that it does not require any special checks or extra function calls every time you want to show a localized string. The index function only runs when we look up a key that doesnt already have a value; under normal circumstances, getting a translation requires only a simple table lookup.

1.	local L = setmetatable({}, { __index = function(t, k)
2.		local v = tostring(k)
3.		rawset(t, k, v)
4.		return v
5.	end })
Here we can see the translation table code from above. Lets walk through it line by line and find out how it works.
  1. Line 1:
    • Weve defined the local variable L, highlighted in yellow, containing the original table {}, highlighted in magenta.
    • Weve attached a metatable to it using the setmetatable Lua function.
    • The metatable is another table, with a key named __index, highlighted in teal.
    • The value of this key is a function that defines what happens when we try to index (look up) a nonexistent key in the original table. Such functions, inside metatables listed under special keywords, are called metamethods.
    • If we look up a key that does exist, this function doesn't run, and we just do a simple table lookup to get the value of that key.
  2. Line 2:
    • The index function first turns the supplied key k, highlighted in green, into a string using the tostring Lua function, and assigns the string to the variable v.
  3. Line 3:
    • It then adds the string v, under the key k, into the table t.
    • t here refers to the original table {} (on Line 1), since the first argument passed to a metatables index function is the table to which the metatable is attached. L also points to this table object.
    • The rawset Lua function is, in practice, the same as assigning t[k] = v, except that it bypasses any metamethods related to adding keys and setting values. In this case, there are no such methods, but its a good habit to use rawset when working with metatables, to avoid any problems.
  4. Line 4:
    • Finally, we return the string v back to whatever code originally tried to find it in the translation table.
    • This is necessary because by the time this function is running, a simple table lookup like L["Goodbye!"] has already failed (because there is no key in the table L named Goodbye!), so if we do not supply an alternate return value, then the original code will get a nil value just as if there were no metatable.
    • Even if we dont return a value here, and the code gets a nil value this time, it will get the proper string v the next time it looks up the same key, because our index metamethod has already added it to the table.
  5. Line 5 simply closes the __index function, the metatable, and the call to setmetatable.

The rest of the code simply checks for the users current language, using the GetLocale WoW API function, and then adds the appropriate translations to the table.

We dont need to specifically add the English words and phrases, because those are the defaults. If we look up L["Goodbye!"] while playing WoW in English, the first time causes the __index function to add a Goodbye! key to the translation table with the value "Goodbye!, and any subsequent lookups will find the Goodbye! key and its value. If we were to add English words and phrases, it would just be a lot of lines like this:

L["Hello!"] = "Hello!"
L["Goodbye!"] = "Goodbye!"
Not very exciting, and not very useful, so we can just skip it and let the metatable magic take care of it.

This also means that when you add a new word or phrase to your addon, it wont instantly break the addon for users in other languages. Of course, without actual translations, the new feature you added the word for might not work (because its looking for Peacebloom instead of Boto-da-paz or Мироцвет) or users in other languages might not be able to understand what the new option does (because it says Do something! instead of Tun Sie etwas! or *가를 해주세요!), but at least your addon will still load, and wont confuse your users with cryptic Lua error messages.

Questions? Comments? Suggestions?

If you need help implementing the code featured in this guide, or if you dont understand something and would like clarification, feel free to post a thread in the WoWInterface Lua/XML Help forum or the WowAce Lua Code Discussion forum.

If you have technical comments, corrections, or suggestions for improving this guide, feel free to send me a private message on this site, or on WowAce.

Please do not send me private messages asking for help with the code in this guide, or with any other code questions. I do not provide private support or tutoring, and I will not answer your questions via private message. At best, you will receive a generic form response with links to the Lua help forums listed above. I do not have time to help everyone on a one-on-one basis for free, and I believe that providing this kind of help privately is against the spirit of a community like WoWInterface anyway. Not only will you get an answer on the forums, but your question and the answer will be available to instantly help anyone else who has the same question tomorrow, or even a year from now.