10.2 Localized Resources
The previous section described how to set locales. After the locale has been set, we then need to address the problem of storing the information corresponding to specific locales. This information can include: textual content, time and date, currency values , pictures, specific files and other view resources. In Go, all of this contextual information is stored in JSON format on our backend, to be called upon and injected into our views when users from specific regions visit our website. For example, English and Chinese content would be stored in en.json and zh-CN.json files, respectively.
Localized textual content
Plain text is the most common way of representing information in web applications, and the bulk of your localized content will likely take this form. The goal is to provide textual content that is both idiomatic to regional expressions and feels natural for foreign users of your site. One solution is to create a nested map of locales, native language strings and their local counterparts. When clients request pages with some textual content, we first check their desired locale, then retrieve the corresponding strings from the appropriate map. The following snippet is a simple example of this process:
package main
import "fmt"
var locales map[string]map[string]string
func main() {
locales = make(map[string]map[string]string, 2)
en := make(map[string]string, 10)
en["pea"] = "pea"
en["bean"] = "bean"
locales["en"] = en
cn := make(map[string]string, 10)
cn["pea"] = "豌豆"
cn["bean"] = "毛豆"
locales["zh-CN"] = cn
lang := "zh-CN"
fmt.Println(msg(lang, "pea"))
fmt.Println(msg(lang, "bean"))
}
func msg(locale, key string) string {
if v, ok := locales[locale]; ok {
if v2, ok := v[key]; ok {
return v2
}
}
return ""
}
The above example sets up maps of translated strings for different locales (in this case, the Chinese and English locales). We map our cn
translations to the same English language keys so that we can reconstruct our English text message in Chinese. If we wanted to switch our text to any other locale we may have implemented, it'd be a simple matter of setting one lang
variable.
Simple key-value substitutions can sometimes be inadequate for our needs. For example, if we had a phrase such as "I am 30 years old" where 30 is a variable, how would we localize it? In cases like these, we can combine use the fmt.Printf
function to achieve the desired result:
en["how old"] = "I am %d years old"
cn["how old"] = "我今年%d岁了"
fmt.Printf(msg(lang, "how old"), 30)
The example code above is only for the purpose of demonstration; actual locale data is typically stored in JSON format in our database, allowing us to execute a simple json.Unmarshal
to populate map locales with our string translations.
Localized date and time
Because of our time zone conventions, the time in one region of the world can be different than the time in another region. Similarly, the way in which time is represented can also vary from locale to locale. For example, a Chinese environment may read 2012年10月24日 星期三 23时11分13秒 CST
, while in English, it might be: Wed Oct 24 23:11:13 CST 2012
. Not only are there variations in language, but there are differences in formatting also. So, when it comes to localizing dates and times, we need to address the following two points:
- time zones
- formatting issues
The $GOROOT/lib/time/package/timeinfo.zip
directory contains locales corresponding to time zone definitions. In order to obtain the time corresponding to a user's current locale, we should first use time.LoadLocation(name string)
to get a Location object corresponding to our locale, passing in a string representing the locale such as Asia/Shanghai
or America/Chicago
. We can then use this Location object in conjunction with a Time object (obtained by calling time.Now
) to get the final time using the Time object's In
method. A detailed look at this process can be seen below (this example uses some of the variables from the example above):
en["time_zone"] = "America/Chicago"
cn["time_zone"] = "Asia/Shanghai"
loc, _ := time.LoadLocation(msg(lang, "time_zone"))
t := time.Now()
t = t.In(loc)
fmt.Println(t.Format(time.RFC3339))
We can handle text formatting in a similar way to solve our time formatting problem:
en["date_format"]="%Y-%m-%d %H:%M:%S"
cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒"
fmt.Println(date(msg(lang,"date_format"),t))
func date(fomat string, t time.Time) string{
year, month, day = t.Date()
hour, min, sec = t.Clock()
//Parsing the corresponding %Y%m%d%H%M%S and then returning the information
//%Y replaced by 2012
//%m replaced by 10
//%d replaced by 24
}
Localized currency value
Obviously, currency differs from region to region also. We can treat it the same way we treated our dates:
en["money"] ="USD %d"
cn["money"] ="¥%d元"
fmt.Println(date(msg(lang,"date_format"),100))
func money_format(fomat string, money int64) string{
return fmt.Sprintf(fomat, money)
}
Localization of views and resources
We can serve customized views with different images, css, js and other static resources depending on the current locale. One way to accomplish this is by organizing these files into their respective locales. Here's an example:
views
|--en //English Templates
|--images //store picture information
|--js //JS files
|--css //CSS files
index.tpl //User Home
login.tpl //Log Home
|--zh-CN //Chinese Templates
|--images
|--js
|--css
index.tpl
login.tpl
With this directory structure, we can render locale-specific views like so:
s1, _ := template.ParseFiles("views" + lang + "index.tpl")
VV.Lang = lang
s1.Execute(os.Stdout, VV)
The resources referenced in the index.tpl
file can be dealt with as follows:
// js file
<script type="text/javascript" src="views/{{.VV.Lang}}/js/jquery/jquery-1.8.0.min.js"></script>
// css file
<link href="views/{{.VV.Lang}}/css/bootstrap-responsive.min.css" rel="stylesheet">
// Picture files
<img src="views/{{.VV.Lang}}/images/btn.png">
With dynamic views and the way we've localized our resources, we will be able to add more locales without much effort.
Summary
This section described how to use and store local resources. We learned that we can use conversion functions and string interpolation for this, and saw that maps can be an effective way of storing locale-specific data. For the latter, we could simply extract the corresponding locale information when needed -if it was textual content we desired, our mapped translations and idioms could be piped directly to the output. If it was something more sophisticated like time or currency, we simply used the fmt.Printf
function to format it before-hand. Localizing our views and resources was the easiest case, and simply involved organizing our files into their respective locales, then referencing them from their locale relative paths.
Links
- Directory
- Previous section: Setting the default region
- Next section: International sites