lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Normally I would agree with this 100%.  Of course every software system is different, and there are a couple of reasons I'm hesitant to replace strings with representations.

First I have stringent memory limits I must stay within.  My concern with creating new variables for each string would be that my heap burden would grow significantly.

Secondly, most of my UI is described in simple XML with the display strings in the actual XML.  Each 'display line' is really an embedded Lua script, something like:

    <Line>"We've been running for " .. GetUptime()</Line>

I realize it's hard on a forum like this to get all the relevant details without flooding the problem space, so thank you for the constructive criticism.

Brett


On Dec 13, 2007 5:17 AM, Eike Decker < eike@cube3d.de> wrote:
Many mails on this topic... but actually the whole discussion is maybe a bit
artificial since using strings just in place is commonly seen as a bad idea.
Let's assume that there is a general message that is displayed in various
places - and thus multiple locations in your sourcecode. If it has a typo in
it, you will need to replace it in all location. And if you miss a string to be
replaced, you end up with different messages.

Actually this whole thing does not need any metatables at all. Just use a table
like in Steve's approach and replace the message table when needed:

text = {}
language = {
 english = {
   error_runtime = "Runtime error",
   error_nosuchlanguage = "The specified language does not exist"
 },
 german = {
   error_runtime = "Laufzeit Fehler",
   error_nosuchlanguage = "Die angegebene Sprache existiert nicht"
 }
}

function setlanguage (lan)
 text = assert(language[lan], text.error_nosuchlanguage)
end

setlanguage("english")

---

In order to construct strings that contain further information a function should
be called to build that string. So the __index metamethod could do that, however
it wouldn't be so wise in my opinion, since it is not very transparent. Like if
it would take the string "The language ${language} ..." and could then use the
global value of the variable "language", but maybe we need a local value to be
put in that cannot be looked up (easily). We could therefore also use a value
lookuptable with values that should be replaced in the strings, but I think
calling a string building function with the given values for the strings would
be more clever. We can use a function with an upvalue to the string so that
each textpattern is a function that is to be called with a replacement table as
argument:

function textformater(str)
 return function (values)
   return (str:gsub("${([^}]-)}",
     function (var) return values[var] or "${"..var.."??}" end))
 end
end

text = {}

language = {
 english = {
  error_nosuchlanguage = "I couldn't find the ${language} in my dictionary",
  error_idk = "I dunno know: ${weirderror}",
  msg_noidea = "no idea what is wrong now",
 },
 german = {
  error_nosuchlanguage = "Eine Sprache namens ${language} existiert "..
    "nicht in meinem Wörterbuch",
  error_idk = "Keine Ahnung: ${weirderror}",
  msg_noidea = "Null ahnung was nun falsch ist",
 }
}

text.error_nosuchlanguage = textformater( language.english.error_nosuchlanguage)

 -- ^^ make sure that this exists on first call

function setlanguage (lan)
 local newtext = {}
 for i,v in pairs(
      assert(language[lan], text.error_nosuchlanguage {language=lan})
 ) do
   newtext[i] = textformater(v)
 end
 text = newtext
end

-- end of the lib, now some examples how to use:
setlanguage("english")
print(text.error_idk { weirderror = text.msg_noidea() })

setlanguage("german")
print(text.error_idk { weirderror = text.msg_noidea() })

setlanguage("french") -- error

It requires a bit more writing, but in my experience that little extrawork is
rewarded when looking over the code again - named parameterlists much easier to
understand than argument lists.
The syntax could also be varied to make it a bit shorter.
It could also be checked if the dictionary has all the strings that are required
- by counterchecking it with the previous dictionary.

In the end, there are many flexible ways to implement a localization system. I
would just discourage the use of strings inside the source code like
error("somemessage") - I do this too, but it doesn't matter in my case since I
don't publish the scripts I make. If I would, I would extract all the strings
and replace it in one of the ways described above once I see the need for it
(like localization).


Eike

> On Dec 12, 2007 9:36 PM, Brett Kugler <bkugler@gmail.com> wrote:
> > I realize I could simplify my life with a little extra notation, but the
> > hope was to really not change my existing code at all and have the string
> > literals be reinterpreted based on a new metamethod instead.  Am I
> grasping
> > at straws?
>
> This solution is akin to Stefan's: we create a special table 'msg'
> which has an __index metamethod which returns the appropriate string
> version of the message word.
>
> -- messages.lua
> local translations = {
>     DidNotWork = {
>         "Operation did not succeed",
>         "Operasie het nie geslaag nie"
>     },
>     NoAnswer = {
>         "No answer found",
>         "Antwoord nie gefind nie",
>     }
> }
>
> msg = {
>     English = 1,
>     Afrikaans = 2,
> }
>
> function get_translation(t,word)
>     local res = translations[word][msg.language]
>     if not res then return "cannot find translation" end
>     return res
> end
>
> setmetatable(msg,{__index=get_translation})
>
> And it would be used like this:
>
> require 'messages"
> msg.language = msg.Afrikaans
> print(msg.DidNotWork)
> print(msg.NoAnswer)
>
>
> steve d.
>