forked from justinklemm/i18n-strings-files
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.coffee
234 lines (202 loc) · 7.38 KB
/
index.coffee
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
fs = require('fs')
Iconv = require('iconv').Iconv
i18nStringsFiles = ->
i18nStringsFiles.prototype.readFile = (file, options, callback) ->
encoding = null
wantsComments = false
# check if encoding was excluded and callback specified as 2nd param
if typeof callback == "undefined" and typeof options == "function"
callback = options
encoding = null
else if typeof options == "string" # for backward compatibility
encoding = options
else if typeof options == "object"
encoding = options['encoding']
wantsComments = options['wantsComments']
# read passed in file
fs.readFile file, (err, buffer) =>
# if there's an error, callback with it and return
if err then return callback?(err, null)
# convert buffer from file into utf-8 string, then parse
str = @convertBufferToString(buffer, encoding)
data = @parse(str, wantsComments)
# callback with parsed object
callback?(null, data)
i18nStringsFiles.prototype.readFileSync = (file, options) ->
encoding = null
wantsComments = false
if typeof options == 'string'
encoding = options
else if typeof options == 'object'
encoding = options['encoding']
wantsComments = options['wantsComments']
# read the passed in file and convert to utf-8 string
buffer = fs.readFileSync(file)
str = @convertBufferToString(buffer, encoding)
# pass file contents string to parse() and return
return @parse(str, wantsComments)
i18nStringsFiles.prototype.writeFile = (file, data, options, callback) ->
encoding = null
wantsComments = false
# check if encoding was excluded and callback specified as 2nd param
if typeof callback == "undefined" and typeof options == "function"
callback = options
encoding = null
else if typeof options == "string" # for backward compatibility
encoding = options
else if typeof options == "object"
encoding = options['encoding']
wantsComments = options['wantsComments']
# build string and convert from utf-8 to output buffer
str = @compile(data, options)
buffer = @convertStringToBuffer(str, encoding)
# write buffer to file
fs.writeFile file, buffer, (err) =>
# callback with any errors
callback?(err)
i18nStringsFiles.prototype.writeFileSync = (file, data, options) ->
encoding = null
wantsComments = false
if typeof options == 'string'
encoding = options
else if typeof options == 'object'
encoding = options['encoding']
wantsComments = options['wantsComments']
# build string and convert from utf-8 to output buffer
str = @compile(data, options)
buffer = @convertStringToBuffer(str, encoding)
# write buffer to file
return fs.writeFileSync(file, buffer)
i18nStringsFiles.prototype.convertBufferToString = (buffer, encoding) ->
# if no encoding is passed in, default to utf-16 (as recommended by Apple)
if !encoding then encoding = 'UTF-16'
# convert buffer to utf-8 string and return
iconv = new Iconv(encoding, 'UTF-8')
return iconv.convert(buffer).toString('utf8')
i18nStringsFiles.prototype.convertStringToBuffer = (str, encoding) ->
# if no encoding is passed in, default to utf-16 (as recommended by Apple)
if !encoding then encoding = 'UTF-16'
# convert string from utf-8 to buffer in output encoding
iconv = new Iconv('UTF-8', encoding)
return iconv.convert(str)
i18nStringsFiles.prototype.parse = (input, wantsComments) ->
# if wantsComments is not specified, default to false
if !wantsComments then wantsComments = false
# patterns used for parsing
reAssign = /[^\\]" = "/
reLineEnd = /";$/
reCommentEnd = /\*\/$/
# holds resulting hash
result = {}
# splt into lines
lines = input.split("\n")
# previous comment
currentComment = ''
currentValue = ''
currentId = ''
nextLineIsComment = false
nextLineIsValue = false
# process line by line
lines.forEach (line) ->
# strip extra whitespace
line = line.trim()
# normalize spacing around assignment operator
line = line.replace(/([^\\])("\s*=\s*")/g, "$1\" = \"")
# remove any space between final quote and semi-colon
line = line.replace(/"\s+;/g, '";')
# check if starts with '/*', store it in currentComment var
if nextLineIsComment
if line.search(reCommentEnd) == -1
currentComment += '\n' + line.trim()
return
else
nextLineIsComment = false
currentComment += '\n' + line.substr(0, line.search(reCommentEnd)).trim()
return
else if line.substr(0, 2) == '/*' && !nextLineIsValue
if line.search(reCommentEnd) == -1
nextLineIsComment = true
currentComment = line.substr(2).trim()
return
else
nextLineIsComment = false
currentComment = line.substr(2, line.search(reCommentEnd)-2).trim()
return
msgid = ''
msgstr = ''
if line == '' && !nextLineIsValue
return
# check if starts with '/*', store it in currentComment var
if nextLineIsValue
if line.search(reLineEnd) == -1
currentValue += '\n' + line.trim()
return
else
nextLineIsValue = false
currentValue += '\n' + line.substr(0, line.search(reLineEnd)).trim()
msgid = currentId
msgstr = currentValue
currentId = ''
currentValue = ''
else if line.search(reLineEnd) == -1 && !nextLineIsComment
nextLineIsValue = true
currentId = line
currentId = currentId.substr(1)
currentId = currentId.substr(0, currentId.search(reAssign) + 1)
currentId = currentId.replace(/\\"/g, "\"")
currentValue = line
currentValue = currentValue.substr(currentValue.search(reAssign) + 6)
return
else
# get msgid
msgid = line
msgid = msgid.substr(1)
msgid = msgid.substr(0, msgid.search(reAssign) + 1)
# get msg str
msgstr = line
msgstr = msgstr.substr(msgstr.search(reAssign) + 6)
msgstr = msgstr.substr(0, msgstr.search(reLineEnd))
# convert escaped quotes
msgid = msgid.replace(/\\"/g, "\"")
msgstr = msgstr.replace(/\\"/g, "\"")
# convert escaped new lines
msgid = msgid.replace(/\\n/g, "\n")
msgstr = msgstr.replace(/\\n/g, "\n")
# store values in object
if !wantsComments then result[msgid] = msgstr
else
val = { 'text': msgstr }
if currentComment
val['comment'] = currentComment
currentComment = ''
result[msgid] = val
# return resulting object
return result
i18nStringsFiles.prototype.compile = (data, wantsComments) ->
# if wantsComments is not specified, default to false
if !wantsComments then wantsComments = false
# make sure data is an object
if typeof data != "object" then return ""
# output string
output = ""
# loop through hash
for msgid, val of data
msgstr = ''
comment = null
if typeof val == 'string' then msgstr = val
else
if val.hasOwnProperty('text') then msgstr = val['text']
if wantsComments and val.hasOwnProperty('comment') then comment = val['comment']
# escape quotes in msgid, msgstr
msgid = msgid.replace(/"/g, "\\\"")
msgstr = msgstr.replace(/"/g, "\\\"")
# escape new lines in msgid, msgstr
msgid = msgid.replace(/\n/g, "\\n")
msgstr = msgstr.replace(/\r?\n/g, "\\n")
# add comment if available
if comment then output = output + "/* " + comment + " */\n"
# add line to output
output = output + "\"" + msgid + "\" = \"" + msgstr + "\";\n"
# return output string
return output
module.exports = new i18nStringsFiles