String access & metamethods

classic Classic list List threaded Threaded
32 messages Options
12
Reply | Threaded
Open this post in threaded view
|

String access & metamethods

Brett Kugler
I've been using Lua for a while now, but really have kept things fairly simple as far as some of the more 'interesting' Lua features.

I'm now attempting to implement localization to my Lua apps and thought this would be a good time to look into using metamethods, as the solution I've thought up seems to call for them.  Unfortunately, I've never used them and what's more, I'm not sure what I'm attempting can be accomplished even with that powerful tool in my pocket.

So here's the skinny.  I would like every literal string reference the VM encounters to be compared to a table of localized strings and return either the localized version, or the original string if it's not in the table.  Here's a quick code snippet (Lua 5.1):

t={}
t["english1"]={"english1","spanish1","french1"}
t["english2"]={"english2","spanish2","french2"}
t["english3"]={"english3","spanish3","french3"}

language=2 -- Spanish

print("english1")    <- spanish1
a_word="english2"  <-  a_word="spanish2"

function(in_string)
    if t[in_string][language] ~= nil then
        return t[in_string][language]
    else
        return in_string
end

So now the question.  How do I make the string access in print("english1") trigger the metamethod of a string and which metamethod would that be?  Since I also want this to be generic, it really should work across any string access at any point in the execution.  What I mean by this is that the triggering of the lookup function should be tied to the string literal, not the function using it ( foo("english1") should also return the result of the lookup function.  How about statements like a_word="english1"?

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?

Thanks for any advice in advance,
Brett
Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Stefan Sandberg
One era ago I wrote an appender for lualogging for localization, with some trickery from sir Rici, which ended up being quite useful.

http://giger.servebeer.com/pics/Development/Lua/localizedAppender.zip

Sample follows:
----

   require "logging.localized"
   localizer = logging.localized( 'dictionary.swedish', logging.null() )
   print( localizedLog:info( { id='SomeIdentifier', SomeParam=1986 } ) )

and dictionary.swedish would look like this:


   module( ... )
   messages = {}
messages.WhatHappenedThen = "På våren '${SomeParam}' brann mina termobyxor upp på en åker."


Brett Kugler wrote:
I've been using Lua for a while now, but really have kept things fairly simple as far as some of the more 'interesting' Lua features.

I'm now attempting to implement localization to my Lua apps and thought this would be a good time to look into using metamethods, as the solution I've thought up seems to call for them. Unfortunately, I've never used them and what's more, I'm not sure what I'm attempting can be accomplished even with that powerful tool in my pocket.

So here's the skinny. I would like every literal string reference the VM encounters to be compared to a table of localized strings and return either the localized version, or the original string if it's not in the table. Here's a quick code snippet (Lua 5.1):

t={}
t["english1"]={"english1","spanish1","french1"}
t["english2"]={"english2","spanish2","french2"}
t["english3"]={"english3","spanish3","french3"}

language=2 -- Spanish

print("english1")    <- spanish1
a_word="english2"  <-  a_word="spanish2"

function(in_string)
    if t[in_string][language] ~= nil then
        return t[in_string][language]
    else
        return in_string
end

So now the question. How do I make the string access in print("english1") trigger the metamethod of a string and which metamethod would that be? Since I also want this to be generic, it really should work across any string access at any point in the execution. What I mean by this is that the triggering of the lookup function should be tied to the string literal, not the function using it ( foo("english1") should also return the result of the lookup function. How about statements like a_word="english1"?

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?

Thanks for any advice in advance,
Brett


Reply | Threaded
Open this post in threaded view
|

RE: String access & metamethods

Jerome Vuarand-2
In reply to this post by Brett Kugler
Brett Kugler wrote:
> So here's the skinny.  I would like every literal string reference
> the VM encounters to be compared to a table of localized strings and
> return either the localized version, or the original string if it's
> not in the table.  Here's a quick code snippet (Lua 5.1):   

You can't do that in pure Lua, because in some cases no metamethod is
involved (see below).

> t={}
> t["english1"]={"english1","spanish1","french1"}
> t["english2"]={"english2","spanish2","french2"}
> t["english3"]={"english3","spanish3","french3"}
> 
> language=2 -- Spanish
> 
> print("english1")    <- spanish1
> a_word="english2"  <-  a_word="spanish2"

Here you have a simple assignation to a variable. If that variable is
local, no metamethod is triggered. If it's global, it's highly dependent
on how the environment is set up.

> 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?   

The extra notation can be as small as a single character per string.
Here is an example:

t={}
t["english1"]={"english1","spanish1","french1"}
t["english2"]={"english2","spanish2","french2"}
t["english3"]={"english3","spanish3","french3"}
language = 2

L = function(english)
   return t[english][language]
end

print(L"english1")   --> spanish1, note the L prefix
a_word = L"english2" --> a_word == "spanish2"
print(a_word)        --> spanish2


Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Lucas Hermann Negri
In reply to this post by Brett Kugler
You can use GNU gettext. Is portable and easy to bind.

Em Qua, 2007-12-12 Ãs 13:36 -0600, Brett Kugler escreveu:
> I've been using Lua for a while now, but really have kept things
> fairly simple as far as some of the more 'interesting' Lua features.


Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Brett Kugler
In reply to this post by Jerome Vuarand-2
I think I could probably live with the one additional character per string, although that syntax is noodling my brain right now.  I will have to play with it to figure out how L"english1" turns into what would amount to L("english1").

Thanks for both responses so far.

Brett

On Dec 12, 2007 2:15 PM, Jerome Vuarand <[hidden email] > wrote:
Brett Kugler wrote:
> So here's the skinny.  I would like every literal string reference
> the VM encounters to be compared to a table of localized strings and
> return either the localized version, or the original string if it's
> not in the table.  Here's a quick code snippet (Lua 5.1):

You can't do that in pure Lua, because in some cases no metamethod is
involved (see below).

> t={}
> t["english1"]={"english1","spanish1","french1"}
> t["english2"]={"english2","spanish2","french2"}
> t["english3"]={"english3","spanish3","french3"}
>
> language=2 -- Spanish
>
> print("english1")    <- spanish1
> a_word="english2"  <-  a_word="spanish2"

Here you have a simple assignation to a variable. If that variable is
local, no metamethod is triggered. If it's global, it's highly dependent
on how the environment is set up.

> 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?

The extra notation can be as small as a single character per string.
Here is an example:

t={}
t["english1"]={"english1","spanish1","french1"}
t["english2"]={"english2","spanish2","french2"}
t["english3"]={"english3","spanish3","french3"}
language = 2

L = function(english)
  return t[english][language]
end

print(L"english1")   --> spanish1, note the L prefix
a_word = L"english2" --> a_word == "spanish2"
print(a_word)        --> spanish2

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Luiz Henrique de Figueiredo
In reply to this post by Jerome Vuarand-2
> You can't do that in pure Lua, because in some cases no metamethod is
> involved (see below).

sure there is. try this:

getmetatable("").__tostring=function (x) return "<<<"..x..">>>" end

for k,v in next,string do print(k,v) end
print("hello")

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Brett Kugler
OK, so that's just brilliant, and was exactly what I was looking for.  Now the question is, what am I potentially getting myself into by overwriting the __tostring metamethod?

Just to give everyone a better idea of my code structure, I'm not using modules and all functions are in the global space, so getting the global metatable like below should work fine for me.

t={}
t["english1"]={"english1","spanish1","french1"}
t["english2"]={"english2","spanish2","french2"}
t["english3"]={"english3","spanish3","french3"}

language=2 -- Spanish

getmetatable("").__tostring=function(in_string)
    if t[in_string] ~= nil and t[in_string][language] ~= nil then
        return t[in_string][language]
    else
        return in_string
    end
end

print("english1")

a_value="english2"
print(a_value)

print("notintable")

Thanks again, I love this language and the fine people who support it.
Brett

On Dec 12, 2007 3:43 PM, Luiz Henrique de Figueiredo <[hidden email]> wrote:
> You can't do that in pure Lua, because in some cases no metamethod is
> involved (see below).

sure there is. try this:

getmetatable("").__tostring=function (x) return "<<<"..x..">>>" end

for k,v in next,string do print(k,v) end
print("hello")

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Luiz Henrique de Figueiredo
> OK, so that's just brilliant, and was exactly what I was looking for.  Now
> the question is, what am I potentially getting myself into by overwriting
> the __tostring metamethod?

The __tostring metamethod is only used in print and in tostring; it's not
a core metamethod. I'm not sure this answers your question but there it is.

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Eric Tetz
In reply to this post by Brett Kugler
On Dec 12, 2007 2:02 PM, Brett Kugler <[hidden email]> wrote:
> t={}
> t["english1"]={"english1","spanish1","french1"}
> t["english2"]={"english2","spanish2","french2"}
> t["english3"]={"english3","spanish3","french3"}

You don't need the English version twice. This will do just as well:

t["english1"]={"spanish1","french1"}
t["english2"]={"spanish2","french2"}
t["english3"]={"spanish3","french3"}

Since your __tostring function defaults to the English string anyway.

> getmetatable("").__tostring=function(in_string)
>     if t[in_string] ~= nil and t[in_string][language] ~= nil then
>
>         return t[in_string][language]
>     else
>         return in_string
>     end
> end

There's a handy Lua idiom for code like that:

getmetatable("").__tostring = function(s)
    return t[s] and t[s][language] or s
end

> print("english1")
> a_value="english2"
> print(a_value)

You should be aware that this works because print calls 'tostring' on
it's arguments. You will not get automatic translation in other
places:

   io.write("english1")

   error("english1")

   print("english1" .. "english1")

You would need to explicitly call tostring():

   io.write(tostring("english1"))

   error(tostring("english1"))

   print(tostring("english1") .. tostring("english1"))

At that point, you're better off just calling a localization function,
because it's *clearer*.  It's easier to maintain straightforward code
than 'clever' code.

In this case you have a more bigger maintenance issue: duplicating data.

   localized = {
     ["Record not found. Contact system administator"]={"spanish
version","french version"},
     ...
   }

   ...

   print("Record not found. Contact system administator")


You have the same string literal in two different places. If you're
looking over the code later and notice that you misspelled
"administrator", you need to remember to fix it in both places or your
localization breaks. Worse, there's nothing at the point of use (the
print statement) to indicate that localization is happening. The
code's not self documenting, and easy broken.

Better to just use a identifier for the error message (like
RECORD_NOT_FOUND, which is defined according to the current language)
rather than a literal string which automagically changes value.

Cheers,
Eric

Reply | Threaded
Open this post in threaded view
|

RE: String access & metamethods

Carlos Augusto Teixeira Mendes-3
In reply to this post by Luiz Henrique de Figueiredo
Be carefull with concatenation operations.

In the code:

getmetatable("").__tostring=function (x) return "<<<"..x..">>>" end

a = "foo".."bar"
print(a)

you will get <<<foobar>>>

and not <<<foo>>><<<bar>>> as you might be expecting for your translation
requirements.

Carlos Augusto.

> -----Original Message-----
> From: [hidden email]
> [[hidden email] Behalf Of Luiz
> Henrique de Figueiredo
> Sent: Wednesday, December 12, 2007 8:27 PM
> To: Lua list
> Subject: Re: String access & metamethods
>
>
> > OK, so that's just brilliant, and was exactly what I was
> looking for.  Now
> > the question is, what am I potentially getting myself into by
> overwriting
> > the __tostring metamethod?
>
> The __tostring metamethod is only used in print and in tostring; it's not
> a core metamethod. I'm not sure this answers your question but
> there it is.
> No virus found in this incoming message.
> Checked by AVG.
> Version: 7.5.503 / Virus Database: 269.17.1/1181 - Release Date:
> 12/11/2007 5:05 PM
>
No virus found in this outgoing message.
Checked by AVG.
Version: 7.5.503 / Virus Database: 269.17.1/1181 - Release Date: 12/11/2007
5:05 PM


Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Luiz Henrique de Figueiredo
> Be carefull with concatenation operations.

Like I said, the __tostring metamethod is not a core metamethod.

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Ken Smith-2
Here is an alternative to the excellent metamethod technique.  If you
output text via other means than print, this may incur more work since
you'll have to rewrite each of the output methods separately.

#!/usr/bin/env lua

original = {}
original.print = print

translations = {
   french =
   {
      ["english phrase 1"] = "french phrase 1",
      ["english phrase 2"] = "french phrase 2",
   },
   portuguese =
   {
      ["english phrase 1"] = "portuguese phrase 1",
      ["english phrase 2"] = "portuguese phrase 2",
   },
   spanish =
   {
      ["english phrase 1"] = "spanish phrase 1",
      ["english phrase 2"] = "spanish phrase 2",
   },
}

language = "portuguese"

print = function(...)
   local b = {insert = table.insert}

   for i,a in ipairs(arg) do
      if not translations[language] then
         -- unsupported language, fail gracefully
         translations[language] = {}
      end
      b:insert(translations[language][a] or a)
   end

   original.print(unpack(b))
end

print("english phrase 1", "hi", "english phrase 2")

   Ken Smith

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Ken Smith-2
On Dec 12, 2007 3:27 PM, Ken Smith <[hidden email]> wrote:
> Here is an alternative to the excellent metamethod technique.  If you
> output text via other means than print, this may incur more work since
> you'll have to rewrite each of the output methods separately.

A more careful read of your mail shows me my suggestion does not fit
your original requirements so I apologize for the noise.  Perhaps
there is still something in there you can use.

   Ken Smith

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Miles Bader-2
In reply to this post by Carlos Augusto Teixeira Mendes-3
"Carlos Augusto Teixeira Mendes" <[hidden email]> writes:
> you will get <<<foobar>>>
>
> and not <<<foo>>><<<bar>>> as you might be expecting for your translation
> requirements.

Yeah, I think for this sort of task, you really want _source_ strings to
be translated.

I think it's better to byte the bullet and use the prefix (e.g. L"...")
though, because translating _every_ string by default seems way too
dangerous.  Some strings are not meant to be translated, and only the
programmer really knows which is which.

[Consider:

   display_dialog_box ("succeeded")

versus

   if output_of_some_script == "succeeded" then
      ...
   end

It's pretty likely the first should be translated but not the second.]

-Miles

-- 
`Cars give people wonderful freedom and increase their opportunities.
 But they also destroy the environment, to an extent so drastic that
 they kill all social life' (from _A Pattern Language_)

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Brett Kugler
Yes, in the end I agree.  The __tostring thing is neat, but it fails to work in instances outside of print() etc.  So the original translation function L would seem to meet my needs the best.

Again, I thank all of you for your comments and help.

Brett

On Dec 12, 2007 5:50 PM, Miles Bader <[hidden email]> wrote:
"Carlos Augusto Teixeira Mendes" <[hidden email]> writes:
> you will get <<<foobar>>>
>
> and not <<<foo>>><<<bar>>> as you might be expecting for your translation
> requirements.

Yeah, I think for this sort of task, you really want _source_ strings to
be translated.

I think it's better to byte the bullet and use the prefix (e.g. L"...")
though, because translating _every_ string by default seems way too
dangerous.  Some strings are not meant to be translated, and only the
programmer really knows which is which.

[Consider:

  display_dialog_box ("succeeded")

versus

  if output_of_some_script == "succeeded" then
     ...
  end

It's pretty likely the first should be translated but not the second.]

-Miles

--
`Cars give people wonderful freedom and increase their opportunities.
 But they also destroy the environment, to an extent so drastic that
 they kill all social life' (from _A Pattern Language_)

Reply | Threaded
Open this post in threaded view
|

RE: String access & metamethods

Jerome Vuarand-2
To ease even more the use of a L function (or any other name you see fit), you can use a token filter to add it to all strings. Since you'll probably some strings to be untranslated (all strings that are not UI strings), you can make the token filter only add the L prefix to strings using double quotes, and let strings using simple quotes go untranslated (I don't know if a token filter can distinguish between the two, but that would be a nice addition if it doesn't).


From: [hidden email] [mailto:[hidden email]] On Behalf Of Brett Kugler
Sent: Wednesday, December 12, 2007 7:17 PM
To: Lua list
Subject: Re: String access & metamethods

Yes, in the end I agree.  The __tostring thing is neat, but it fails to work in instances outside of print() etc.  So the original translation function L would seem to meet my needs the best.

Again, I thank all of you for your comments and help.

Brett

On Dec 12, 2007 5:50 PM, Miles Bader <[hidden email]> wrote:
"Carlos Augusto Teixeira Mendes" <[hidden email]> writes:
> you will get <<<foobar>>>
>
> and not <<<foo>>><<<bar>>> as you might be expecting for your translation
> requirements.

Yeah, I think for this sort of task, you really want _source_ strings to
be translated.

I think it's better to byte the bullet and use the prefix (e.g. L"...")
though, because translating _every_ string by default seems way too
dangerous.  Some strings are not meant to be translated, and only the
programmer really knows which is which.

[Consider:

  display_dialog_box ("succeeded")

versus

  if output_of_some_script == "succeeded" then
     ...
  end

It's pretty likely the first should be translated but not the second.]

-Miles

--
`Cars give people wonderful freedom and increase their opportunities.
 But they also destroy the environment, to an extent so drastic that
 they kill all social life' (from _A Pattern Language_)

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Luiz Henrique de Figueiredo
> To ease even more the use of a L function (or any other name you see
> fit), you can use a token filter to add it to all strings.

But that does not work for concatenations: for instance, "log" and "in"
may have translations but not "login".

> (I don't know if a token filter can distinguish between the two, but
> that would be a nice addition if it doesn't).

It can't because, by definition, it seems strings *after* the lexer has
turned text into tokens. All strings are the same, regardless of what
text form was used to define them.
--lhf

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

steve donovan
In reply to this post by Brett Kugler
On Dec 12, 2007 9:36 PM, Brett Kugler <[hidden email]> 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.

Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Eike Decker-2
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 <[hidden email]> 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.
> 




Reply | Threaded
Open this post in threaded view
|

Re: String access & metamethods

Brett Kugler
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 < [hidden email]> 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 <[hidden email]> 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.
>




12