You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

454 lines
13KB

  1. /*
  2. * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
  3. * Licensed under the MIT License. See LICENSE file in the project root for full license information.
  4. */
  5. package gotext
  6. import (
  7. "bytes"
  8. "encoding/gob"
  9. "os"
  10. "path/filepath"
  11. "sync"
  12. )
  13. /*
  14. Locale wraps the entire i18n collection for a single language (locale)
  15. It's used by the package functions, but it can also be used independently to handle
  16. multiple languages at the same time by working with this object.
  17. Example:
  18. import (
  19. "encoding/gob"
  20. "bytes"
  21. "fmt"
  22. "git.deineagentur.com/DeineAgenturUG/gotext"
  23. )
  24. func main() {
  25. // Create Locale with library path and language code
  26. l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
  27. // Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.{po,mo}'
  28. l.AddDomain("default")
  29. // Translate text from default domain
  30. fmt.Println(l.Get("Translate this"))
  31. // Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.{po,mo}')
  32. l.AddDomain("extras")
  33. // Translate text from domain
  34. fmt.Println(l.GetD("extras", "Translate this"))
  35. }
  36. */
  37. type Locale struct {
  38. // Path to locale files.
  39. path string
  40. // Language for this Locale
  41. lang string
  42. // List of available Domains for this locale.
  43. Domains map[string]Translator
  44. // First AddDomain is default Domain
  45. defaultDomain string
  46. // Sync Mutex
  47. sync.RWMutex
  48. }
  49. // NewLocale creates and initializes a new Locale object for a given language.
  50. // It receives a path for the i18n .po/.mo files directory (p) and a language code to use (l).
  51. func NewLocale(p, l string) *Locale {
  52. return &Locale{
  53. path: p,
  54. lang: SimplifiedLocale(l),
  55. Domains: make(map[string]Translator),
  56. }
  57. }
  58. //SetLang ...
  59. func (l *Locale) SetLang(lang string) {
  60. l.lang = lang
  61. }
  62. //GetLang ...
  63. func (l *Locale) GetLang() string {
  64. return l.lang
  65. }
  66. //SetPath ...
  67. func (l *Locale) SetPath(path string) {
  68. l.path = path
  69. }
  70. func (l *Locale) findExt(dom, ext string) string {
  71. filename := filepath.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
  72. if _, err := os.Stat(filename); err == nil {
  73. return filename
  74. }
  75. if len(l.lang) > 2 {
  76. filename = filepath.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
  77. if _, err := os.Stat(filename); err == nil {
  78. return filename
  79. }
  80. }
  81. filename = filepath.Join(l.path, l.lang, dom+"."+ext)
  82. if _, err := os.Stat(filename); err == nil {
  83. return filename
  84. }
  85. if len(l.lang) > 2 {
  86. filename = filepath.Join(l.path, l.lang[:2], dom+"."+ext)
  87. if _, err := os.Stat(filename); err == nil {
  88. return filename
  89. }
  90. }
  91. return ""
  92. }
  93. // AddDomain creates a new domain for a given locale object and initializes the Po object.
  94. // If the domain exists, it gets reloaded.
  95. func (l *Locale) AddDomain(dom string) {
  96. var poObj Translator
  97. file := l.findExt(dom, "po")
  98. if file != "" {
  99. poObj = new(Po)
  100. // Parse file.
  101. poObj.ParseFile(file)
  102. } else {
  103. file = l.findExt(dom, "mo")
  104. if file != "" {
  105. poObj = new(Mo)
  106. // Parse file.
  107. poObj.ParseFile(file)
  108. } else {
  109. // fallback return if no file found with
  110. return
  111. }
  112. }
  113. // Save new domain
  114. l.Lock()
  115. if l.Domains == nil {
  116. l.Domains = make(map[string]Translator)
  117. }
  118. if l.defaultDomain == "" {
  119. l.defaultDomain = dom
  120. }
  121. l.Domains[dom] = poObj
  122. // Unlock "Save new domain"
  123. l.Unlock()
  124. }
  125. // AddTranslator takes a domain name and a Translator object to make it available in the Locale object.
  126. func (l *Locale) AddTranslator(dom string, tr Translator) {
  127. l.Lock()
  128. if l.Domains == nil {
  129. l.Domains = make(map[string]Translator)
  130. }
  131. if l.defaultDomain == "" {
  132. l.defaultDomain = dom
  133. }
  134. l.Domains[dom] = tr
  135. l.Unlock()
  136. }
  137. // GetDomain is the domain getter for Locale configuration
  138. func (l *Locale) GetDomain() string {
  139. l.RLock()
  140. dom := l.defaultDomain
  141. l.RUnlock()
  142. return dom
  143. }
  144. // SetDomain sets the name for the domain to be used.
  145. func (l *Locale) SetDomain(dom string) {
  146. l.Lock()
  147. l.defaultDomain = dom
  148. l.Unlock()
  149. }
  150. // Get uses a domain "default" to return the corresponding Translation of a given string.
  151. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  152. func (l *Locale) Get(str string, vars ...interface{}) string {
  153. return l.GetD(l.GetDomain(), str, vars...)
  154. }
  155. // GetN retrieves the (N)th plural form of Translation for the given string in the "default" domain.
  156. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  157. func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
  158. return l.GetND(l.GetDomain(), str, plural, n, vars...)
  159. }
  160. // GetD returns the corresponding Translation in the given domain for the given string.
  161. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  162. func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
  163. // Sync read
  164. l.RLock()
  165. defer l.RUnlock()
  166. if l.Domains != nil {
  167. if _, ok := l.Domains[dom]; ok {
  168. if l.Domains[dom] != nil {
  169. return l.Domains[dom].Get(str, vars...)
  170. }
  171. }
  172. }
  173. return Printf(str, vars...)
  174. }
  175. // GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
  176. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  177. func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
  178. // Sync read
  179. l.RLock()
  180. defer l.RUnlock()
  181. if l.Domains != nil {
  182. if _, ok := l.Domains[dom]; ok {
  183. if l.Domains[dom] != nil {
  184. return l.Domains[dom].GetN(str, plural, n, vars...)
  185. }
  186. }
  187. }
  188. // Use western default rule (plural > 1) to handle missing domain default result.
  189. if n == 1 {
  190. return Printf(str, vars...)
  191. }
  192. return Printf(plural, vars...)
  193. }
  194. // GetC uses a domain "default" to return the corresponding Translation of the given string in the given context.
  195. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  196. func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
  197. return l.GetDC(l.GetDomain(), str, ctx, vars...)
  198. }
  199. // GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
  200. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  201. func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
  202. return l.GetNDC(l.GetDomain(), str, plural, n, ctx, vars...)
  203. }
  204. // GetDC returns the corresponding Translation in the given domain for the given string in the given context.
  205. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  206. func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
  207. // Sync read
  208. l.RLock()
  209. defer l.RUnlock()
  210. if l.Domains != nil {
  211. if _, ok := l.Domains[dom]; ok {
  212. if l.Domains[dom] != nil {
  213. return l.Domains[dom].GetC(str, ctx, vars...)
  214. }
  215. }
  216. }
  217. return Printf(str, vars...)
  218. }
  219. // GetNDC retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
  220. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  221. func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
  222. // Sync read
  223. l.RLock()
  224. defer l.RUnlock()
  225. if l.Domains != nil {
  226. if _, ok := l.Domains[dom]; ok {
  227. if l.Domains[dom] != nil {
  228. return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
  229. }
  230. }
  231. }
  232. // Use western default rule (plural > 1) to handle missing domain default result.
  233. if n == 1 {
  234. return Printf(str, vars...)
  235. }
  236. return Printf(plural, vars...)
  237. }
  238. // GetE uses a domain "default" to return the corresponding Translation of a given string.
  239. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  240. // The second return value is true iff the string was found.
  241. func (l *Locale) GetE(str string, vars ...interface{}) (string, bool) {
  242. return l.GetDE(l.GetDomain(), str, vars...)
  243. }
  244. // GetNE retrieves the (N)th plural form of Translation for the given string in the "default" domain.
  245. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  246. // The second return value is true iff the string was found.
  247. func (l *Locale) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
  248. return l.GetNDE(l.GetDomain(), str, plural, n, vars...)
  249. }
  250. // GetDE returns the corresponding Translation in the given domain for the given string.
  251. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  252. // The second return value is true iff the string was found.
  253. func (l *Locale) GetDE(dom, str string, vars ...interface{}) (string, bool) {
  254. // Sync read
  255. l.RLock()
  256. defer l.RUnlock()
  257. if l.Domains != nil {
  258. if _, ok := l.Domains[dom]; ok {
  259. if l.Domains[dom] != nil {
  260. return l.Domains[dom].GetE(str, vars...)
  261. }
  262. }
  263. }
  264. return "", false
  265. }
  266. // GetNDE retrieves the (N)th plural form of Translation in the given domain for the given string.
  267. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  268. // The second return value is true iff the string was found.
  269. func (l *Locale) GetNDE(dom, str, plural string, n int, vars ...interface{}) (string, bool) {
  270. // Sync read
  271. l.RLock()
  272. defer l.RUnlock()
  273. if l.Domains != nil {
  274. if _, ok := l.Domains[dom]; ok {
  275. if l.Domains[dom] != nil {
  276. return l.Domains[dom].GetNE(str, plural, n, vars...)
  277. }
  278. }
  279. }
  280. // Use western default rule (plural > 1) to handle missing domain default result.
  281. return "", false
  282. }
  283. // GetCE uses a domain "default" to return the corresponding Translation of the given string in the given context.
  284. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  285. // The second return value is true iff the string was found.
  286. func (l *Locale) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
  287. return l.GetDCE(l.GetDomain(), str, ctx, vars...)
  288. }
  289. // GetNCE retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
  290. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  291. // The second return value is true iff the string was found.
  292. func (l *Locale) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
  293. return l.GetNDCE(l.GetDomain(), str, plural, n, ctx, vars...)
  294. }
  295. // GetDCE returns the corresponding Translation in the given domain for the given string in the given context.
  296. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  297. func (l *Locale) GetDCE(dom, str, ctx string, vars ...interface{}) (string, bool) {
  298. // Sync read
  299. l.RLock()
  300. defer l.RUnlock()
  301. if l.Domains != nil {
  302. if _, ok := l.Domains[dom]; ok {
  303. if l.Domains[dom] != nil {
  304. return l.Domains[dom].GetCE(str, ctx, vars...)
  305. }
  306. }
  307. }
  308. return "", false
  309. }
  310. // GetNDCE retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
  311. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  312. // The second return value is true iff the string was found.
  313. func (l *Locale) GetNDCE(dom, str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
  314. // Sync read
  315. l.RLock()
  316. defer l.RUnlock()
  317. if l.Domains != nil {
  318. if _, ok := l.Domains[dom]; ok {
  319. if l.Domains[dom] != nil {
  320. return l.Domains[dom].GetNCE(str, plural, n, ctx, vars...)
  321. }
  322. }
  323. }
  324. // Use western default rule (plural > 1) to handle missing domain default result.
  325. return "", false
  326. }
  327. // LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
  328. type LocaleEncoding struct {
  329. Path string
  330. Lang string
  331. Domains map[string][]byte
  332. DefaultDomain string
  333. }
  334. // MarshalBinary implements encoding BinaryMarshaler interface
  335. func (l *Locale) MarshalBinary() ([]byte, error) {
  336. obj := new(LocaleEncoding)
  337. obj.DefaultDomain = l.defaultDomain
  338. obj.Domains = make(map[string][]byte)
  339. for k, v := range l.Domains {
  340. var err error
  341. obj.Domains[k], err = v.MarshalBinary()
  342. if err != nil {
  343. return nil, err
  344. }
  345. }
  346. obj.Lang = l.lang
  347. obj.Path = l.path
  348. var buff bytes.Buffer
  349. encoder := gob.NewEncoder(&buff)
  350. err := encoder.Encode(obj)
  351. return buff.Bytes(), err
  352. }
  353. // UnmarshalBinary implements encoding BinaryUnmarshaler interface
  354. func (l *Locale) UnmarshalBinary(data []byte) error {
  355. buff := bytes.NewBuffer(data)
  356. obj := new(LocaleEncoding)
  357. decoder := gob.NewDecoder(buff)
  358. err := decoder.Decode(obj)
  359. if err != nil {
  360. return err
  361. }
  362. l.defaultDomain = obj.DefaultDomain
  363. l.lang = obj.Lang
  364. l.path = obj.Path
  365. // Decode Domains
  366. l.Domains = make(map[string]Translator)
  367. for k, v := range obj.Domains {
  368. var tr TranslatorEncoding
  369. buff := bytes.NewBuffer(v)
  370. trDecoder := gob.NewDecoder(buff)
  371. err := trDecoder.Decode(&tr)
  372. if err != nil {
  373. return err
  374. }
  375. l.Domains[k] = tr.GetTranslator()
  376. }
  377. return nil
  378. }