Projekt

Obecné

Profil

Stáhnout (5.13 KB) Statistiky
| Větev: | Revize:
1
var fs = require('fs')
2
var path = require('path')
3
var util = require('util')
4

    
5
function Y18N (opts) {
6
  // configurable options.
7
  opts = opts || {}
8
  this.directory = opts.directory || './locales'
9
  this.updateFiles = typeof opts.updateFiles === 'boolean' ? opts.updateFiles : true
10
  this.locale = opts.locale || 'en'
11
  this.fallbackToLanguage = typeof opts.fallbackToLanguage === 'boolean' ? opts.fallbackToLanguage : true
12

    
13
  // internal stuff.
14
  this.cache = {}
15
  this.writeQueue = []
16
}
17

    
18
Y18N.prototype.__ = function () {
19
  if (typeof arguments[0] !== 'string') {
20
    return this._taggedLiteral.apply(this, arguments)
21
  }
22
  var args = Array.prototype.slice.call(arguments)
23
  var str = args.shift()
24
  var cb = function () {} // start with noop.
25

    
26
  if (typeof args[args.length - 1] === 'function') cb = args.pop()
27
  cb = cb || function () {} // noop.
28

    
29
  if (!this.cache[this.locale]) this._readLocaleFile()
30

    
31
  // we've observed a new string, update the language file.
32
  if (!this.cache[this.locale][str] && this.updateFiles) {
33
    this.cache[this.locale][str] = str
34

    
35
    // include the current directory and locale,
36
    // since these values could change before the
37
    // write is performed.
38
    this._enqueueWrite([this.directory, this.locale, cb])
39
  } else {
40
    cb()
41
  }
42

    
43
  return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args))
44
}
45

    
46
Y18N.prototype._taggedLiteral = function (parts) {
47
  var args = arguments
48
  var str = ''
49
  parts.forEach(function (part, i) {
50
    var arg = args[i + 1]
51
    str += part
52
    if (typeof arg !== 'undefined') {
53
      str += '%s'
54
    }
55
  })
56
  return this.__.apply(null, [str].concat([].slice.call(arguments, 1)))
57
}
58

    
59
Y18N.prototype._enqueueWrite = function (work) {
60
  this.writeQueue.push(work)
61
  if (this.writeQueue.length === 1) this._processWriteQueue()
62
}
63

    
64
Y18N.prototype._processWriteQueue = function () {
65
  var _this = this
66
  var work = this.writeQueue[0]
67

    
68
  // destructure the enqueued work.
69
  var directory = work[0]
70
  var locale = work[1]
71
  var cb = work[2]
72

    
73
  var languageFile = this._resolveLocaleFile(directory, locale)
74
  var serializedLocale = JSON.stringify(this.cache[locale], null, 2)
75

    
76
  fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) {
77
    _this.writeQueue.shift()
78
    if (_this.writeQueue.length > 0) _this._processWriteQueue()
79
    cb(err)
80
  })
81
}
82

    
83
Y18N.prototype._readLocaleFile = function () {
84
  var localeLookup = {}
85
  var languageFile = this._resolveLocaleFile(this.directory, this.locale)
86

    
87
  try {
88
    localeLookup = JSON.parse(fs.readFileSync(languageFile, 'utf-8'))
89
  } catch (err) {
90
    if (err instanceof SyntaxError) {
91
      err.message = 'syntax error in ' + languageFile
92
    }
93

    
94
    if (err.code === 'ENOENT') localeLookup = {}
95
    else throw err
96
  }
97

    
98
  this.cache[this.locale] = localeLookup
99
}
100

    
101
Y18N.prototype._resolveLocaleFile = function (directory, locale) {
102
  var file = path.resolve(directory, './', locale + '.json')
103
  if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) {
104
    // attempt fallback to language only
105
    var languageFile = path.resolve(directory, './', locale.split('_')[0] + '.json')
106
    if (this._fileExistsSync(languageFile)) file = languageFile
107
  }
108
  return file
109
}
110

    
111
// this only exists because fs.existsSync() "will be deprecated"
112
// see https://nodejs.org/api/fs.html#fs_fs_existssync_path
113
Y18N.prototype._fileExistsSync = function (file) {
114
  try {
115
    return fs.statSync(file).isFile()
116
  } catch (err) {
117
    return false
118
  }
119
}
120

    
121
Y18N.prototype.__n = function () {
122
  var args = Array.prototype.slice.call(arguments)
123
  var singular = args.shift()
124
  var plural = args.shift()
125
  var quantity = args.shift()
126

    
127
  var cb = function () {} // start with noop.
128
  if (typeof args[args.length - 1] === 'function') cb = args.pop()
129

    
130
  if (!this.cache[this.locale]) this._readLocaleFile()
131

    
132
  var str = quantity === 1 ? singular : plural
133
  if (this.cache[this.locale][singular]) {
134
    str = this.cache[this.locale][singular][quantity === 1 ? 'one' : 'other']
135
  }
136

    
137
  // we've observed a new string, update the language file.
138
  if (!this.cache[this.locale][singular] && this.updateFiles) {
139
    this.cache[this.locale][singular] = {
140
      one: singular,
141
      other: plural
142
    }
143

    
144
    // include the current directory and locale,
145
    // since these values could change before the
146
    // write is performed.
147
    this._enqueueWrite([this.directory, this.locale, cb])
148
  } else {
149
    cb()
150
  }
151

    
152
  // if a %d placeholder is provided, add quantity
153
  // to the arguments expanded by util.format.
154
  var values = [str]
155
  if (~str.indexOf('%d')) values.push(quantity)
156

    
157
  return util.format.apply(util, values.concat(args))
158
}
159

    
160
Y18N.prototype.setLocale = function (locale) {
161
  this.locale = locale
162
}
163

    
164
Y18N.prototype.getLocale = function () {
165
  return this.locale
166
}
167

    
168
Y18N.prototype.updateLocale = function (obj) {
169
  if (!this.cache[this.locale]) this._readLocaleFile()
170

    
171
  for (var key in obj) {
172
    this.cache[this.locale][key] = obj[key]
173
  }
174
}
175

    
176
module.exports = function (opts) {
177
  var y18n = new Y18N(opts)
178

    
179
  // bind all functions to y18n, so that
180
  // they can be used in isolation.
181
  for (var key in y18n) {
182
    if (typeof y18n[key] === 'function') {
183
      y18n[key] = y18n[key].bind(y18n)
184
    }
185
  }
186

    
187
  return y18n
188
}
(4-4/5)