We have detected that cookies are not enabled on your browser. Please enable cookies to ensure the proper experience.
Results 1 to 25 of 59

Threaded View

  1. #10
    Join Date
    Mar 2007
    Posts
    1,590

    Internationalization or "How Vindar Saved the World"

    Many developers tend to forget that LoTRO is an international application supporting three client languages, English, German and French (and to a limited extent, a fourth language, Russian). When developing a plugin for the general public, it is a good idea to separate out all of the text strings. There are two basic approaches to this, one is to create one table and load it with only the currently selected language strings. The other is to use a table that has a separate index for the language. Either way, all references to static text should provide some means of translating them to each of the three client languages. If you aren't comfortable translating your interface, ask someone on the forums to assist you, there are many friendly multi-lingual people that will be glad to help you.

    A more troublesome problem arises when saving and reloading data. Since the user has the option to change which language his client is supporting and not only the character sets but the numeric formatting is different between English and German/French, this can cause some serious problems. The first problem of supporting the UTF-8 characters for non-English clients has several solutions, I have chosen to implement a variant of the patch originally published by Vindar. This patch creates a wrapper for the standard Turbine data save and load methods which encodes the data before saving it and decodes it when it is reloaded so that UTF-8 characters are properly saved and loaded. The Vindar Patch can be found on LoTROInterface.com at http://www.lotrointerface.com/downlo...anclients.html

    The Vindar Patch does not by itself solve the numeric formatting problem. Fortunately, there is a fairly simple solution to this. Since numeric data is saved as strings via the Vindar Patch, you can coerce the string into the correct format when reloading. Create a global variable to track whether european formatting or english formatting is currently in use:
    Code:
    euroFormat=(tonumber("1,000")==1); -- will be true if the number is formatted with a comma for decimal place, false otherwise
    -- now create a function for automatically converting a number in string format to its correct numeric value
    if euroFormat then
        function euroNormalize(value)
            return tonumber((string.gsub(value,"%.",",")));
        end
    else
        function euroNormalize(value)
            return tonumber((string.gsub(value,",",".")));
        end
    end
    then whenever you load a saved numeric value, force it to the current number format. The following example assumes data was stored for the current character
    Code:
     Opacity=1; -- default
     local settings=PatchDataLoad( Turbine.DataScope.Character, "Settings");
     if settings~=nil then
       Opacity=euroNormalize(settings.Opacity);
     end
    Note that PatchDataLoad is the wrapper for the PluginData.Load method from the Vindar Patch. Not only will this allow saving and loading data in the DE/FR clients, this has the added benefit of automatically adjusting the numeric format if a client changes from DE/FR to EN or EN to DE/FR between saving and loading the data.

    Another internationalization issue that can arise is automatically detecting the current client locale setting. The built in function GetLocale() returns the operating system locale, NOT the current client application locale. Lua authors developed a workaround but Turbine eventually responded by implementing the Turbine.Engine:GetLanguage() method which returns one of the Turbine.Language values:
    Turbine.Language.Invalid=0
    Turbine.Language.English=2
    Turbine.Language.EnglishGB=268 435457 (0x10000001)
    Turbine.Language.French=268435 459 (0x10000003)
    Turbine.Language.German=268435 460 (0x10000004)
    Turbine.Language.Russian=26843 5463 (0x10000007)

    The different clients have different chat commands and chat messages - for instance in the french client, you don't use "/plugins load HelloWorld", you would instead use "/plugins charger HelloWorld". This can become a significant issue when creating quickslot alias commands or when using the Chat object to trap incomming messages. You can use the locale test above to determine the running client and then create the appropriate command.

    Another issue has to do with creating resource strings. Some people have created their .lua files with UTF8 encoding with success, but I prefer a slightly more "brute force" approach. Anywhere that I need a special character, I simply concatenate the appropriate character codes to generate the desired character. For instance to generate a cedilla (the french "c" with a squiggle under it which indicates a soft c) I use "\195\167" - see the example below. The "" character escapes the character code and the cedilla is character code 195 + character code 167. This is essentially the same as string.char(195)..string.char( 167).

    The last issue has to do with the lack of text metrics in LoTRO Lua. Different languages will need different amounts of space for their translation of a string. In most programming languages, you would simply use a function to determine how much space a string requires. Unfortunately, Turbine did not provide us with such a luxury. However, there is a workaround using the Visibility attribute of a bound scroll bar and a non-multiline label control. Set the label control's width to a small number (a good estimate for the 20 point fonts would be 8 pixels per character in the string) and then slowly increase the width of the label (I usually use increments of 8 to save time) until the scrollbar:IsVisible() returns false. Once the scrollbar detects that it no longer needs to be rendered, you will know that you have enough room for the text. Ideally, you create one such label with a bound scrollbar and re-use it as needed to test all strings. You should probably hide the label and the scrollbar off canvass by setting their top properties to a negative value. By obtaining the metrics, you will know if you need to increase the size of a control or possibly indicate that text has been cropped.

    This leads me to the last of the "Hello World" samples. This one will remember where it was loaded and will display the message in the correct client language (of course, you'll have to close and restart the client, selecting a different language to test it )
    Code:
     import "Turbine"; -- the base Turbine namespace
     import "Turbine.UI"; -- this will expose the label control that we will implement
     import "Turbine.UI.Lotro"; -- this will expose the standard window that we will implement
     locale = "en";
     if Turbine.Shell.IsCommand("hilfe") then
      locale = "de";
     elseif Turbine.Shell.IsCommand("aide") then
      locale = "fr";
     end
     strings={}; -- create a table for the string resources - note, this would usually be generated in a separate .lua file
     strings["en"]={}; -- create the English resource string table
     strings["en"][1]="Hello World Window"
     strings["en"][2]="Hello World"
     strings["en"][3]="English"
     strings["de"]={}; -- create the German resource string table
     strings["de"][1]="Hallo Welt Fenster"
     strings["de"][2]="Hallo Welt"
     strings["de"][3]="Deutsch"
     strings["fr"]={}; -- create the French resource string table
     strings["fr"][1]="Fen\195\170tre Bonjour tout le monde"
     strings["fr"][2]="Bonjour tout le monde"
     strings["fr"][3]="Fran\195\167aise";
     function UnloadPlugin()
      if HelloWindow~=nil then
       local settings={}
       settings["top"]=HelloWindow:GetTop();
       settings["left"]=HelloWindow:GetLeft();
       Turbine.PluginData.Save(Turbine.DataScope.Account, "HelloWorld", settings);
      end
     end
     HelloWindow=Turbine.UI.Lotro.Window(); -- we call the constructor of the standard window object to create an instance
     local x,y=Turbine.UI.Display:GetWidth()/2-100,Turbine.UI.Display:GetHeight()/2-100;
     local settings=Turbine.PluginData.Load(Turbine.DataScope.Account, "HelloWorld");
     if settings~=nil then
      if settings["top"]~=nil then y=settings["top"] end
      if settings["left"]~=nil then x=settings["left"] end
     end
     HelloWindow.loaded=false;
     HelloWindow:SetWantsUpdates(false);
     HelloWindow.Update=function()
      if not HelloWindow.loaded then
       HelloWindow.loaded=true;
       Plugins["HelloWorld"].Unload = function(self,sender,args)
        UnloadPlugin();
       end
       HelloWindow:SetWantsUpdates(false);
      end
     end
     if locale=="fr" then
      HelloWindow:SetSize(350,200);
     elseif locale=="de" then
      HelloWindow:SetSize(260,200);
     else
      HelloWindow:SetSize(280,200);
     end
     HelloWindow:SetPosition(x,y);
     HelloWindow:SetText(strings[locale][1]); -- assigns the title bar text
     HelloWindow.Message=Turbine.UI.Label(); -- create a label control to display our message
     HelloWindow.Message:SetParent(HelloWindow); -- sets the label as a child of the main window
     HelloWindow.Message:SetSize(HelloWindow:GetWidth()-20,20); -- sets the message size
     HelloWindow.Message:SetPosition(10,90); -- places the message control in the vertical middle of the window with a 10 pixel left and right border
     HelloWindow.Message:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter); -- centers the text in the message control both horizontally and vertically
     HelloWindow.Message:SetText(strings[locale][2]); -- sets the actual message text
     HelloWindow:SetWantsUpdates(true);
     HelloWindow:SetVisible(true); -- display the window (windows are not visible by default)
    Last edited by Garan; Jun 27 2022 at 07:32 PM. Reason: typo

 

 

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

This form's session has expired. You need to reload the page.

Reload