├── README.md ├── LICENSE.md └── i18n.lua /README.md: -------------------------------------------------------------------------------- 1 | # i18n 2 | 3 | Internationalization functions for LÖVE. We might rename it to something more interesting later. 4 | 5 | # Specifying a language 6 | 7 | It's just a Lua table. 8 | 9 | Several real-world examples can be found [here, in our LD33 entry](https://github.com/excessive/ludum-dare-33/tree/master/assets/lang). 10 | 11 | ```lua 12 | return { 13 | locale = "en", 14 | base = "assets/sounds/en", 15 | quotes = { "\"", "\"" }, -- NYI, but planned. Several languages use different quotes. 16 | strings = { 17 | ["main/play"] = { text = "Play", audio = "play.ogg" }, 18 | ["main/options"] = { text = "Options" }, 19 | -- etc 20 | } 21 | } 22 | ``` 23 | 24 | # Usage 25 | ```lua 26 | local lang = require "i18n" 27 | 28 | -- load all your language files. the filenames are of no significance. 29 | lang:load("languages/en.lua") 30 | 31 | -- set default locale if a string is not available in the current one. 32 | -- Note: will not return audio for fallback language (that would be really weird). 33 | lang:set_fallback("en") 34 | 35 | -- set locale to source data from 36 | lang:set_locale("en") 37 | 38 | -- translated string, path to audio, and whether the string is from fallback. 39 | -- can also be written as lang:get("key") 40 | lang "main/play" -- => "Play", "assets/sounds/en/play.ogg", false 41 | ``` 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # i18n 2 | 3 | This code is licensed under the [**MIT Open Source License**][MIT]. 4 | 5 | Copyright (c) 2015 Landon Manning - LManning17@gmail.com - [LandonManning.com][LM] 6 | Copyright (c) 2015 Colby Klein - shakesoda@gmail.com - [excessive.moe][exmoe] 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | [MIT]: http://www.opensource.org/licenses/mit-license.html 28 | [LM]: http://LandonManning.com 29 | [exmoe]: http://excessive.moe -------------------------------------------------------------------------------- /i18n.lua: -------------------------------------------------------------------------------- 1 | local i18n = {} 2 | i18n.__index = i18n 3 | i18n.__call = function(self, key) 4 | return self:get(key) 5 | end 6 | 7 | local d = console and console.d or print 8 | local i = console and console.i or print 9 | local e = console and console.e or print 10 | 11 | local ok, memoize = pcall(require, "memoize") 12 | if not ok then 13 | i("Memoize not available. Using passthrough.") 14 | memoize = function(f) 15 | return f 16 | end 17 | end 18 | 19 | local function new() 20 | return setmetatable({ 21 | locale = false, 22 | fallback = false, 23 | strings = {}, 24 | }, i18n) 25 | end 26 | 27 | function i18n:load(file) 28 | if not love.filesystem.isFile(file) then 29 | return false 30 | end 31 | local locale 32 | local bork = function(msg) 33 | e(string.format("Error loading locale %s: %s", file, tostring(msg))) 34 | return false 35 | end 36 | local ok, msg = pcall(function() 37 | local ok, chunk = pcall(love.filesystem.load, file) 38 | if not ok then 39 | return bork(chunk) 40 | end 41 | local data = chunk() 42 | 43 | -- Sanity check! 44 | assert(type(data) == "table") 45 | assert(type(data.locale) == "string") 46 | assert(type(data.base) == "string") 47 | assert(type(data.quotes) == "table") 48 | assert(#data.quotes == 2) 49 | assert(type(data.strings) == "table") 50 | 51 | locale = data 52 | end) 53 | if not ok then 54 | return bork(msg) 55 | end 56 | 57 | locale.strings.base = locale.base 58 | self.strings[locale.locale] = locale.strings 59 | 60 | i(string.format("Loaded locale \"%s\" from \"%s\"", locale.locale, file)) 61 | self:invalidate_cache() 62 | 63 | return true 64 | end 65 | 66 | function i18n:set_fallback(locale) 67 | self:invalidate_cache() 68 | self.fallback = locale 69 | end 70 | 71 | function i18n:set_locale(locale) 72 | self:invalidate_cache() 73 | self.locale = locale 74 | end 75 | 76 | -- Returns 3 values: text, audio, fallback. 77 | -- - Text is mandatory and is guaranteed to be a string. 78 | -- - Audio is optional and will return the full path to the audio clip for the 79 | -- key. If missing, will return false. 80 | -- - Fallback will be true if the key was missing from your selected language, 81 | -- but present in the fallback locale. 82 | local function gen_get() 83 | return function(self, key) 84 | assert(type(key) == "string", "Expected key of type 'string', got type '"..type(key).."'") 85 | local lang = self.strings[self.locale] 86 | local fallback = false 87 | if not lang or type(lang) == "table" and not lang[key] then 88 | lang = self.strings[self.fallback] 89 | fallback = true 90 | end 91 | if lang and type(lang[key]) == "table" and type(lang[key].text) == "string" then 92 | local value = lang[key] 93 | if fallback then 94 | -- d(string.format( 95 | -- "String \"%s\" missing from locale %s, using fallback", 96 | -- key, self.locale 97 | -- )) 98 | end 99 | -- Do not return audio for different languages if we're falling back. 100 | -- The voice mismatch would be strange, subtitles-only is better. 101 | return value.text, (fallback == false) and (value.audio and string.format("%s/%s", lang.base, value.audio) or false) or false, fallback 102 | else 103 | d(string.format( 104 | "String \"%s\" missing from locale %s and fallback (%s)", 105 | key, self.locale, self.fallback 106 | )) 107 | return key, false, false 108 | end 109 | end 110 | end 111 | 112 | function i18n:invalidate_cache() 113 | self._get_internal = gen_get() 114 | end 115 | 116 | function i18n:get(key) 117 | if not self._get_internal then 118 | self:invalidate_cache() 119 | end 120 | return memoize(self._get_internal)(self, key) 121 | end 122 | 123 | return setmetatable({new=new},{__call=function(_,...) return new(...) end}) 124 | --------------------------------------------------------------------------------