Archive

UI Mod Guide - Chapter 6

Updated Thu, Feb 21, 2008 by bpirkle

Krimm's Guide
to World of Warcraft Addon Design

Part 6
Localization



Part 6 Index

Download Part 6 Example Files!


Viva la WoW!  Despite two years of sleeping through high school Spanish, I can barely order at Taco Bell.  But I think that means “Long Live WoW!”.  This brings up a question:  what about the many WoW players who don’t speak English?  All the text embedded into our AddOn has thus far been in English.  What can we do so that our AddOns work just as well in all languages supported by WoW?  Enter the magic of “localization”, the process of making your AddOns seamlessly change language to match the user.

 

Part Six of this guide will describe localization.  Compared to Part Five, this will be a short installment.  I originally planned to discuss tables, packages, and localization all in Part Four.  But tables took enough space to justify their own section, then packages did the same.  This leaves us knowing almost everything necessary to localize our AddOn already.  Cool.

HOW DO WE KNOW WHAT LANGUAGE THE USER PREFERS?

The first thing we need to know is how WoW tells us which language we should present to the user.  WoW is available in the following languages:  English, French, German, and Korean.  WoW provides the GetLocale() function to return a code identifying the current language.  These codes are simply strings.  The possible values are:

            enUS = English

            frFR = French

            deDE = German

            koKR = Korean

 

As of this writing (March 2006), a Spanish version has been officially announced but is still in development (maybe I shouldn’t have slept through high school Spanish after all!).  I heard a rumor that the code will be esES, but that’s not confirmed.  I’ll update this guide once I’m sure.

 

In any case, once our code supports two languages it is very easy to extend to more:  our code can call GetLocale() to see which language is in effect, then return the correct string.  Simple enough! 

 

HOW DO WE PRESENT MULTIPLE LANGAGUES TO THE USER?

 

Given the GetLocale() routine, there are a bunch of ways you could localize your AddOn.  For example, you could just scatter if/else/elseif throughout your code, testing for the locale every time you want to use a constant string.  But that would quickly degenerate into a rat’s nest. 

 

We’ve been careful all along to put our constant strings at the top of our .lua files.  Now let’s take that a step further.  In Part Five, we placed all our saved variables into a dedicated saved variables table.  We’ll take a similar approach to localization.  We’ll create one “constant strings” table for each language, each in its own .lua file.  Then we’ll arrange these files so that only one table is actually in use throughout the AddOn.  First, here’s the English version, which we’ll call localization.lua:

 

--  English constant strings

TthCs6 = {};

 

TthCs6.BATTLE_CRIES_ARE = "Battle cries are:  ";

TthCs6.BATTLE_CRY_ADDED = "Battle cry added:  ";

TthCs6.BATTLE_CRIES_CLEARED = "Battle cries cleared.";

 

Now, here’s the French version, which we’ll call localization.fr.lua:

 

--  French constant strings

if ( GetLocale() == "frFR" ) then

  TthCs6 = {};

 

  TthCs6.BATTLE_CRIES_ARE = "Les cris de guerre sont:  ";

  TthCs6.BATTLE_CRY_ADDED = "Cri de guerre ajouté:  ";

  TthCs6.BATTLE_CRIES_CLEARED = "Cris de guerre effacés.";

end

 

Notice that in the English version, we didn’t call GetLocale(), but in the French version we did.  If the user is running the English version of WoW, the English version of the TthCs6 (Cs for “Constant strings”) table will be created and we’ll use it.  If the user is running the French version of WoW, the English version will still be created, but the French one will replace it.  We’ll end up using the French one. 

 

To make this happen, we need to add these new .lua files to our .xml.  Here are the <Script> elements after adding the new files:

 

       <Script file="localization.lua"/>

       <Script file="localization.fr.lua"/>

       <Script file="TthSv6.lua"/>

       <Script file="TthBc6.lua"/>

       <Script file="TthAog6.lua"/>

 

Simple enough!

 

HOW DO WE CREATE THE LANGUAGE-SPECIFIC LUA FILES?

 

Computer representation of character sets is a complex and (to those of us with twisted minds) fascinating topic.  I won’t even attempt to describe it in this guide.  Suffice it to say that there is an alphabet soup of terms and acronyms like ASCII, Extended-ASCII, EBCDIC (anyone else old enough to remember that one?), UTF-8, WCHAR, Unicode, and so on.  The important one for us is “Unicode”.  That’s the computer industry’s current best hope for representing characters in all languages in a consistent way.  And fortunately, WoW speaks Unicode. 

 

Practically speaking, here’s the one thing you need to know for creating your localized .lua files:  use a Unicode editor to create the file.  Like I said, I don’t speak anything besides English.  (After reading my writing for six installments of this guide, you’re probably wondering if I even speak that! J)  So to create the French translation of the battle cry AddOn, I emailed my localization.lua file to a friend who speaks French.  She translated the strings, using an Unicode editor, and sent them back to me.  Even though I still don’t speak a word of French, my AddOn now does.  Way cool.

DRESSING UP THE .TOC FILE

There’s one more nice touch we can add.  In our .toc file, we can localize the Title and Notes fields as well.  We do this by including translated versions directly in the .toc file.  The language code is appended to the field name (with a dash in between).  WoW then knows to automatically pick up the translated version.  If we hadn’t done this, WoW would still have displayed the default (English) Title and Notes, even for users running the French version of WoW.  See below for the full code listing, which includes these changes.

THE CODE

Our little AddOn is up to seven files now.  Here they are:

 

TthAog6.toc:

 

## Interface: 11000

## Title: TenTonHammer Guide Part 6

## Title-frFR: TenTonHammer Guide 6e partie

## Author:  TenTonHammer.com

## Notes:  Makes your character scream a randomized battle cry when combat begins

## Notes-frFR:  Permet à votre personnage de crier un cri de guerre choisi au hasard lorsque le combat commence

## SavedVariablesPerCharacter: TthAog6Sv

TthAog6.xml

 

TthAog6.xml:

 

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/">

       <Script file="localization.lua"/>

       <Script file="localization.fr.lua"/>

       <Script file="TthSv6.lua"/>

       <Script file="TthBc6.lua"/>

       <Script file="TthAog6.lua"/>

       <Frame name="TthAog6Core">

              <Scripts>

                     <OnLoad>TthAog6:Load();</OnLoad>

                     <OnEvent>TthAog6:Event();</OnEvent>

              </Scripts>

       </Frame>

</Ui>

 

TthAog6.lua:

 

--  TenTonHammer.com AddOn Guide Version 6

TthAog6 = {};

 

TthAog6.ADDON_NAME = "TthAog6";

 

--  Called when the AddOn loads.

function TthAog6:Load()

  -- Register for events.

  this:RegisterEvent("PLAYER_REGEN_DISABLED");

  this:RegisterEvent("ADDON_LOADED");

 

  --  Create the slash commands.

  SlashCmdList["TTH_AOG6_ADDNEWBATTLECRY"] = function(msg)

    TthBc6:AddNew(msg);

  end

  SLASH_TTH_AOG6_ADDNEWBATTLECRY1 = "/addnewbattlecry6";

  SLASH_TTH_AOG6_ADDNEWBATTLECRY2 = "/anbc6";

 

  SlashCmdList["TTH_AOG6_CLEARALLBATTLECRIES"] = function(msg)

    TthBc6:ClearAll();

  end

  SLASH_TTH_AOG6_CLEARALLBATTLECRIES1 = "/clearallbattlecries6";

  SLASH_TTH_AOG6_CLEARALLBATTLECRIES2 = "/cabc6";

end

 

--  Called to handle the events for which we’ve registered.

function TthAog6:Event()

  if (event == "ADDON_LOADED") then

    if (arg1 == self.ADDON_NAME) then

      -- Handle saved variable defaults

      for key, value in pairs(TthAog6SvDefaults) do

        if (not TthAog6Sv[key]) then

          TthAog6Sv[key] = value;

        end

      end

 

      TthBc6:AddOnLoaded();

    end

  elseif (event == "PLAYER_REGEN_DISABLED") then

    TthBc6:SendRandomCry();

  end

end

 

TthBc6.lua:

 

--  TenTonHammer.com Battle Cry Version 6

TthBc6 = {};

 

--  Utility function for printing to chat area

function TthBc6:OutputToChat(msg)

  if (DEFAULT_CHAT_FRAME) then

    DEFAULT_CHAT_FRAME:AddMessage(msg);

  end

end

 

--  Called when the ADDON_LOADED event occurs for our AddOn

function TthBc6:AddOnLoaded()

  self:OutputToChat(TthCs6.BATTLE_CRIES_ARE);

  for i=1,table.getn(TthAog6Sv.Cries) do

    self:OutputToChat(TthAog6Sv.Cries[i]);

  end

end

 

--  Sends a random battle cry to chat (if there are any to send)

function TthBc6:SendRandomCry()

  local numBattleCries = table.getn(TthAog6Sv.Cries);

  if (numBattleCries > 0) then

    local randomKey = math.random(numBattleCries);

    SendChatMessage(TthAog6Sv.Cries[randomKey], TthAog6Sv.OutputType);

  end

end

 

--  Called when the associated slash command executes.

function TthBc6:AddNew(msg)

  -- Add the new battle cry, for use later.

  local newKey = table.getn(TthAog6Sv.Cries) + 1;

  TthAog6Sv.Cries[newKey] = msg;

 

  -- Echo the new battle cry for confirmation.

  local echo = TthCs6.BATTLE_CRY_ADDED .. msg;

  self:OutputToChat(echo);

end

 

--  Called when the associated slash command executes.

function TthBc6:ClearAll()

  --  assign to an empty table for quick and easy clearing.

  TthAog6Sv.Cries = {};

 

  -- Echo the new battle cry for confirmation.

  self:OutputToChat(TthCs6.BATTLE_CRIES_CLEARED);

end

 

TthSv6.lua:

 

--  Saved variables table

TthAog6Sv = {

  --  This table holds our battle cries.

  Cries = {},

  OutputType

};

 

TthAog6SvDefaults = {

  Cries = {

    "Not in the face!",

    "Not in the groin!",

    "Not in the kneecaps!"

  },

  OutputType = "SAY"

};

 

localization.lua:

 

--  English constant strings

TthCs6 = {};

 

TthCs6.BATTLE_CRIES_ARE = "Battle cries are:  ";

TthCs6.BATTLE_CRY_ADDED = "Battle cry added:  ";

TthCs6.BATTLE_CRIES_CLEARED = "Battle cries cleared.";

 

localization.fr.lua:

 

--  French constant strings

if (GetLocale() == "frFR") then

  TthCs6 = {};

 

  TthCs6.BATTLE_CRIES_ARE = "Les cris de guerre sont:  ";

  TthCs6.BATTLE_CRY_ADDED = "Cri de guerre ajouté:  ";

  TthCs6.BATTLE_CRIES_CLEARED = "Cris de guerre effacés.";

end

 

One final point:  notice that in TthAog6.lua we didn’t localize the name of our AddOn.  Some constant strings, such as this one, are truly constant across all languages.  We can continue to treat these as we have all along.

 

Whew!  We’ve pushed through some pretty heavy topics in the last few parts of this guide.  Ready for some fun?  Catch your breath, then move on to Part Seven where we (finally!) get to add a graphical user interface to our AddOn (under construction).


Go to Appendix 1 - Debugging!


WarlockWallpaper.jpg
Five classes that would be excellent additions to World of Warcraft.
Features
Fri, Jun 20, 2014
Mem
HotSAlphaLoad.jpg
Five things players should expect during the Alpha test of Warlords of Draenor.
Features
Fri, Jun 13, 2014
Mem
GG_25_02

I really don't understand racism in the real world. People are what people are, regardless of skin pigmentation or where their ancestors came from. There's really only one real-world race - the Human Race - and I loathe everyone equally.

Opinions
Mon, Jun 09, 2014
gunky
GarrisonTownHall.jpg
A basic guide to Garrisons in Warlords of Draenor.
Basics, Features, Guides
Fri, Jun 06, 2014
Mem

News from around the 'Net