colloquy-1.35.30/
colloquy-1.35.30/data/lang/
colloquy-1.35.30/data/misc/
colloquy-1.35.30/docs/
-- Colloquy i18n (internationalisation)
-- these functions look up tokens in a specific language, and return
-- them, optionally including some parameters.
--
-- It reads from a table called 'lang' keyed on language name, such
-- as "en-gb" etc.  Each of these is a table, keyed on token.  The
-- value can then be either a string, or a function.  If it is a
-- string, parameters are subsitited into the string.  Place holders
-- in the string are $n where n is a number from 0 to 9 for keying
-- purposes.  If the value is a function, then the function is called,
-- passing it the values as parameters, and it takes the string the
-- function returns as the text.

-- example:
-- lang["en-gb"] = {
--   hello = "Hello, $0!",
--   connect = "$0 has connected from $1.",
--   goodbye = function(a, b, c)
--               return format("Wibble %s splat %s goo %s.", a, b, c)
--             end
--   }
--
-- then:
--   getmsg(lang["en-gb"], lang["en-gb"], "hello", "Bob") = "Hello, Bob!"
--   getmsg(lang["en-gb"], lang["en-gb"], "connect", "Bob", "localhost") = 
--      "Bob has connected from localhost."
--   getmsg(lang["en-gb"], lang["en-gb"], "goodbye", "Moo", "Oink", Cluck") =
--      "Wibble Moo splat Oink goo Cluck"
--
-- Tokens can include the result of expanding another token, such that if
-- the token "moo" is defined as "Hello $0, welcome to ${Oink,Pig Meat}" and
-- the token "Oink" is defined as "I love $0" then "moo" might expand to 
-- "Hello Bob, welcome to 'I love Pig Meat'"
--
-- The getmsg function takes a language to look a token up in, a fallback
-- language if the first one doesn't contain the token, the token name,
-- and the parameters for it.  If no token is found, it returns a string
-- with each parameter seperated by spaces.

lang = {}
local loaded = {}
unfoundToken = "(none)"
msgCache = {}  -- pre-expanded strings, keyed on these values \n seperated:
               -- pLang, fLang, token, param1, param2, ... paramN
               -- Also contains number -> key mappings for random replacement
               -- once the cache reaches a maximum size.
msgCacheSize = 0
msgCacheMax = 256
msgCacheLastRemoved = 0
msgCacheHits = 0
msgCacheMisses = 0

randomseed(0) -- naughty, but handy while debugging

function msgCacheState()
  for i = 1, msgCacheSize do
    print(i .. ": " .. gsub(msgCache[i], "\n", ".") .. "=" .. msgCache[msgCache[i]] .. "\n")
  end
end

function flushMsgCache()
  msgCache = {}
  msgCacheSize = 0
end

function searchMsgCache(pLang, fLang, token, params)
  local key = format("%s\n%s\n%s\n", pLang.NAME, fLang.NAME, token)
  for i = 1, getn(params) do
    key = key .. format("%s\n", params[i])
  end
  if( msgCache[key] ) then
    msgCacheHits = msgCacheHits + 1
  else
    msgCacheMisses = msgCacheMisses + 1
  end
  return msgCache[key]
end

function insertMsgCache(msg, pLang, fLang, token, params)
  local pos
  if( msgCacheSize >= msgCacheMax ) then
    -- we need to randomly select an entry to remove
    pos = random(msgCacheMax)
    msgCacheLastRemoved = pos
    msgCache[msgCache[pos]] = nil -- remove the key/value pair
  else
    msgCacheSize = msgCacheSize + 1
    pos = msgCacheSize
  end
  local key = format("%s\n%s\n%s\n", pLang.NAME, fLang.NAME, token)
  for i = 1, getn(params) do
    key = key .. format("%s\n", params[i])
  end
  msgCache[pos] = key
  msgCache[key] = msg
end

local defaultToken = function(...)
  local r = "Unknown token '" .. unfoundToken .. "': "
  for i = 1, getn(arg) do
    r = r .. arg[i]
    if( i < getn(arg) ) then
      r = r .. " "
    end
  end
  return r
end

evalsubtoken = function(pLang, fLang, ft)
  -- takes "${token,params...}"
  local t = {}
  local f;
  if( strsub(ft, 1, 2) == "${" and strsub(ft, -1, -1) == "}" ) then
    -- yes, we need to fiddle this.
    gsub(ft, "([^%,%$%{%}]+)%,?", function(a) tinsert(%t, a) end)
    f = tremove(t, 1)
    tinsert(t, 1, f)
    tinsert(t, 1, fLang)
    tinsert(t, 1, pLang)
    return call(getmsg, t)
  else
    return ft
  end
end

local expandDollars = function(ft, arg)
  for i = 0, getn(arg or {}) - 1 do
    -- we can't use gsub here, because this may contain information from a user,
    -- and as such might contain gsub magic characters.
    -- search for "$n", where n is the number i, and replace it with arg[i + 1]
    local l = strfind(ft, "$" .. i, 1, 1)
    if( l ) then
      ft = strsub(ft, 1, l - 1) .. arg[i + 1] .. strsub(ft, l + 2, -1)
    end
  end
  return ft
end

local subtext = function(pLang, fLang, ft, arg)
  local expandDollars = %expandDollars
  ft = gsub(ft, "(%$%{[%w%d%s$,]+%})", function(x) return evalsubtoken(%pLang, %fLang, %expandDollars(x,%arg)) end)
  return expandDollars(ft, arg)
end

function getmsg(pLang, fLang, token, ...)
  -- pLang: Primary search language
  -- fLang: Fallback search language
  -- token: Token to search for
  -- ...  : Parameters for token

  pLang = pLang or getlang(colloquy.lang)
  fLang = fLang or pLang
  
  local cached = searchMsgCache(pLang, fLang, token, arg)
  if( cached ) then return cached end

  local ft, cL
  -- search pLang for the token, falling back to each of its parents if
  -- it's not there.
  cL = pLang
  repeat
    ft = cL[token]
    if( not ft and cL.PARENT ) then
      cL = getlang(cL.PARENT)
    else
      cL = nil
    end
  until( ft or not cL )
  -- if we've still not found it, try looking in fLang for it, falling
  -- back to each of its parents if it's not there.
  if( not ft ) then
    cL = fLang
    repeat
      ft = cL[token]
      if( not ft and cL.PARENT ) then
        cL = getlang(cL.PARENT)
      else
        cL = nil
      end
    until( ft or not cL )
  end
  -- give up, and give them the default token.
  unfoundToken = token
  ft = ft or %defaultToken

  if( type(ft) == "string" ) then
    local r = %subtext(pLang, fLang, ft, arg)
    insertMsgCache(r, pLang, fLang, token, arg)
    return r
  elseif( type(ft) == "function" ) then
    ft = call(ft, arg)
    ft = %subtext(pLang, fLang, ft)
    return ft
  else
    -- god knows what went wrong.  Return the default.
    return "unknown token type: " .. call(%defaultToken, arg)
  end
end

function getlang(plang)
  if( %loaded[plang] ~= nil ) then
    return %loaded[plang]
  else
    -- OK, it's not loaded.  Let's load it, after having flushed
    -- the msgCache
    flushMsgCache()
    local n = gsub(plang, "[%/%.]", "")
    local f = openfile(colloquy.langs .. n .. ".lua", "r")
    if( not f ) then
      return nil
    else
      closefile(f)
      dofile(colloquy.langs .. n .. ".lua")
      %loaded[n] = lang[n]
      return lang[n]
    end
  end
end

function reload(plang)
  %loaded[plang] = nil
  getlang(plang)
  for i, v in colloquy.connections do
    if( type(i) == "table" ) then
      if( v.lang.NAME == plang ) then
        v.lang = %loaded[plang]
      end
    end
  end
end

function gm(conn, token, ...)
  tinsert(arg, 1, token)
  tinsert(arg, 1, getlang(colloquy.lang))
  tinsert(arg, 1, conn.lang)
  return call(getmsg, arg)
end