Share Your Code

I18N Strings

Posted by bpappin, Posted on October 30, 2011

Java-Like Locale String Resources (i18n)

I found a few examples of locale string resource handling in Corona, but I wasn't satisfied.
I thought Rosetta had a good thing going, but I couldn't use it as I wanted to.

This class allows strings to be added in a genreal to more specific way.

You set up the resources files from most general to most specific.
The first one, strings.i18n, should always be present and contains the default strings that will be used. During development, this is the only one that needs to be maintained.
the default set of strings will be used if no more specific key is found.

To override a string you provide a more specific file, such as strings_en_US.i18n.

For instance, if I'm Canadian or British, I might spell colour with a "u", however if someone from the US download the app, we'd like them to see colour without the "u", so we provide a more specific file that redefines the key "colour" with the value "color".

The order of seeking strings is:

  • strings.i18n
  • strings_{language}.i18n
  • strings_{language}_{country}.i18n

You can also pass an optional string to the new() constructor to change the name of the resource file from "strings.i18n" to "yourname.i18n".

Resource files are in JSON format and are loaded with the built in JSON parser. You do need to make sure the resource files are properly formatted JSON data or you will likely get an error when they are processed.

A call to a key that does not exist will return the string "!!MISSING!!", which makes finding those strings you missed easy, while at the same time not causing the code to crash. e.g. calling i18n:getString("bogus.key") returns !!MISSING!!.

Example:

1
2
i18n = require("class_I18N").new()
print(i18n:getString("colour"))

strings.i18n

1
2
3
4
{
        "colour":"Colour",
        "hello":"Hello World"
}

strings_en_US.i18n

1
2
3
4
{
        "colour":"Color",
        "hello":"Hello World"
}

strings_fr.i18n

1
2
3
4
{
        "colour":"Couleur",
        "hello":"Bonjour tout le monde"
}

Source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
-------------------------------------------------
--
-- class_I18N.lua
-- Author: Brill Pappin, Sixgreen Labs Inc.
--
-- Provides locale specific strings, with more specific values overriding more general values.
--
-- See: 
-- http://developer.anscamobile.com/code/i18n-strings
--
-------------------------------------------------
local json = require("json")
 
I18N = {}
local I18N_mt = { __index = I18N }
 
function I18N.new(resource)  -- The constructor
        local object = { 
                isClass = true,
                resource = resource or "strings",
                language =  system.getPreference("locale", "language") 
                        or system.getPreference("ui", "language"),
                country = system.getPreference( "locale", "country" ),
        }
        
        object["files"] = {
                        object.resource..".i18n",
                        object.resource.."_"..object.language..".i18n",
                        object.resource.."_"..object.language.."_"..object.country..".i18n"
                }
                
        -- Files are processed in order so that finer grained 
        -- message override ones that are more general.
        local strmap = {}
        for i = 1, #object.files do
                local path = system.pathForFile( object.files[i], system.ResourcesDirectory )
        
                if path then
                        local file = io.open( path, "r" )
                        if file then -- nil if no file found
                                local contents = file:read( "*a" )
                                io.close( file )
                                
                                local resmap = json.decode(contents)
                                for key, value in pairs(resmap) do
                                        strmap[key] = value;
                                end
                                
                        end
                end
        end
        
        object["strings"] = strmap
  
  return setmetatable( object, I18N_mt )
end
 
function I18N:getString(key)
        if self.strings[key] then 
                        return self.strings[key]
        else
                return "!!MISSING!!"
        end
end
 
function I18N:toString()
        return "I18N [resource="..self.resource..", language="..self.language..", country="..self.country.."]" 
end
 
return I18N


Replies

bpappin
User offline. Last seen 1 year 48 weeks ago. Offline
Joined: 19 Jan 2011

Just saw a hint that resource files may be excluded on Android if the extension isn't known.

Can anyone confirm this?

Lerg
User offline. Last seen 6 hours 3 min ago. Offline
Joined: 8 May 2011

Nice module. Can't confirm your concern, but using .txt files should be safe enough. Also since android is Java based I presume it should support various Java features like such files.

bpappin
User offline. Last seen 1 year 48 weeks ago. Offline
Joined: 19 Jan 2011

Yes, it does :)

I think the issue is what Corona sees as a resource to be copied when it builds the APK.
In some forum article (I can no longer find) I notice someone saying that *.xyz resource wasn't copied and the suggestion was to give the files a *.settings extension, because they would be copied.

Anyway, i'll resolve this myself eventually when I get around to building for the device :)

Lerg
User offline. Last seen 6 hours 3 min ago. Offline
Joined: 8 May 2011

Just build an apk, open it with an archiver and see whether or not your files are there. Would be faster.