@tim-greller/xgettext-regex 0.5.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/bin/xgettext-regex.js +1 -1
- package/index.js +82 -30
- package/package.json +2 -4
- package/test/test.js +17 -1
package/README.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Minimum viable xgettext .pot file generator. Uses a configurable regex to get translation keys.
|
|
4
4
|
|
|
5
|
+
___________________
|
|
6
|
+
*Note: This repository was forked from [alanshaw/xgettext-regex](https://github.com/alanshaw/xgettext-regex), which is no longer maintained.*
|
|
7
|
+
*It provides the following additional features:*
|
|
8
|
+
|
|
9
|
+
**✨ Improved command line interface (CLI)**
|
|
10
|
+
- **Configurable regex via CLI:** made the regex (and regexTextCaptureIndex) configurable.
|
|
11
|
+
|
|
12
|
+
**✨ Better support for complex syntax & other languages**
|
|
13
|
+
- Added an **improved default regex** and documentation for it.
|
|
14
|
+
- **Multiline extraction support:** rewrote matching to work across newlines, added regex dotAll support.
|
|
15
|
+
- Added support for **Java text blocks** using """.
|
|
16
|
+
- Fix bug of missing location information if two strings found in same line.
|
|
17
|
+
|
|
18
|
+
**✨ Better .pot file diffs to use it in version management like GIT**
|
|
19
|
+
- Deterministic order of the keys by file path
|
|
20
|
+
- Switched to **relative paths** for location comments to prohibit commiting personal workspace directory paths.
|
|
21
|
+
___________________
|
|
22
|
+
|
|
5
23
|
## Examples
|
|
6
24
|
|
|
7
25
|
```sh
|
package/bin/xgettext-regex.js
CHANGED
|
@@ -16,7 +16,7 @@ var outFile = argv.o || argv.outfile
|
|
|
16
16
|
var opts = {
|
|
17
17
|
fn: (argv.f || argv.fn)
|
|
18
18
|
}
|
|
19
|
-
if (argv.r || argv.regex) opts.regex = new RegExp(argv.r || argv.regex, '
|
|
19
|
+
if (argv.r || argv.regex) opts.regex = new RegExp(argv.r || argv.regex, 'gs')
|
|
20
20
|
if (argv.i || argv.index) opts.regexTextCaptureIndex = argv.i || argv.index
|
|
21
21
|
|
|
22
22
|
if (argv._.length) {
|
package/index.js
CHANGED
|
@@ -2,9 +2,7 @@ var fs = require('fs')
|
|
|
2
2
|
var path = require('path')
|
|
3
3
|
var Readable = require('stream').Readable
|
|
4
4
|
var through = require('through2')
|
|
5
|
-
var readdirp = require('readdirp')
|
|
6
5
|
var once = require('once')
|
|
7
|
-
var xtend = require('xtend')
|
|
8
6
|
|
|
9
7
|
function createDuplexStream (filename, opts) {
|
|
10
8
|
filename = filename || ''
|
|
@@ -50,7 +48,7 @@ function createDuplexStream (filename, opts) {
|
|
|
50
48
|
* \) closing paranthesis
|
|
51
49
|
* ) end of positive lookahead
|
|
52
50
|
*/
|
|
53
|
-
opts.regex = opts.regex || new RegExp("(?<=" + opts.fn + "\\(\\s*(?:(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*')\\s*,\\s*)*)(([\"'])(?:(?=(\\\\?))\\3.)*?\\2)(?=(?:\\s*,\\s*(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*'|[^\"')]+))*\\s*\\))", '
|
|
51
|
+
opts.regex = opts.regex || new RegExp("(?<=" + opts.fn + "\\(\\s*(?:(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*')\\s*,\\s*)*)(([\"'])(?:(?=(\\\\?))\\3.)*?\\2)(?=(?:\\s*,\\s*(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*'|[^\"')]+))*\\s*\\))", 'gs')
|
|
54
52
|
opts.regexTextCaptureIndex = opts.regexTextCaptureIndex || 1
|
|
55
53
|
|
|
56
54
|
var chunks = []
|
|
@@ -83,6 +81,16 @@ function createDuplexStream (filename, opts) {
|
|
|
83
81
|
text = '"' + text.replace(/"/g, '\\"') + '"'
|
|
84
82
|
}
|
|
85
83
|
|
|
84
|
+
// handle triple-quoted string (java code blocks)
|
|
85
|
+
if (text.slice(0, 3) == '"""') {
|
|
86
|
+
text = text
|
|
87
|
+
.slice(3, -3)
|
|
88
|
+
.replace(/^\r?\n\s*/, '') // remove initial new line
|
|
89
|
+
.replace(/\\\r?\n\s*/g, '') // escaped linebreak + indent
|
|
90
|
+
.replace(/\r?\n\s*/g, '\\n') // normal linebreak + indent
|
|
91
|
+
text = `"${text}"`
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
this.push([
|
|
87
95
|
``,
|
|
88
96
|
`#: ${relativeFilename}:${lineNum}`,
|
|
@@ -123,11 +131,68 @@ function getText (filename, opts) {
|
|
|
123
131
|
return fs.createReadStream(filename).pipe(createDuplexStream(filename, opts))
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
var
|
|
134
|
+
var DEFAULT_WALK_OPTS = {
|
|
127
135
|
fileFilter: ['!.*', '!*.png', '!*.jpg', '!*.gif', '!*.zip', '!*.gz'],
|
|
128
136
|
directoryFilter: ['!.*', '!node_modules', '!coverage']
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
function createFilter (patterns) {
|
|
140
|
+
var compiled = patterns.map(function (pattern) {
|
|
141
|
+
var isExclude = pattern[0] === '!'
|
|
142
|
+
var glob = isExclude ? pattern.slice(1) : pattern
|
|
143
|
+
var re = new RegExp('^' + glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*') + '$')
|
|
144
|
+
return { re: re, isExclude: isExclude }
|
|
145
|
+
})
|
|
146
|
+
return function (name) {
|
|
147
|
+
for (var i = 0; i < compiled.length; i++) {
|
|
148
|
+
if (compiled[i].re.test(name)) return !compiled[i].isExclude
|
|
149
|
+
}
|
|
150
|
+
return true // include by default when no pattern matches
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function walkDirectorySorted (dir, fileFilter, dirFilter, onFile, cb) {
|
|
155
|
+
fs.readdir(dir, function (er, entries) {
|
|
156
|
+
if (er) return cb(er)
|
|
157
|
+
entries.sort(function (a, b) {
|
|
158
|
+
return a.localeCompare(b, undefined, {sensitivity: 'base'})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
var index = 0
|
|
162
|
+
|
|
163
|
+
function nextEntry () {
|
|
164
|
+
if (index >= entries.length) return cb()
|
|
165
|
+
|
|
166
|
+
var name = entries[index++]
|
|
167
|
+
var fullPath = path.join(dir, name)
|
|
168
|
+
|
|
169
|
+
fs.stat(fullPath, function (er, stats) {
|
|
170
|
+
if (er) {
|
|
171
|
+
console.error('walkDirectorySorted stat error', fullPath, er)
|
|
172
|
+
return nextEntry()
|
|
173
|
+
}
|
|
174
|
+
if (stats.isFile()) {
|
|
175
|
+
if (fileFilter(name)) {
|
|
176
|
+
onFile(fullPath, nextEntry)
|
|
177
|
+
} else {
|
|
178
|
+
nextEntry()
|
|
179
|
+
}
|
|
180
|
+
} else if (stats.isDirectory()) {
|
|
181
|
+
if (dirFilter(name)) {
|
|
182
|
+
walkDirectorySorted(fullPath, fileFilter, dirFilter, onFile, nextEntry)
|
|
183
|
+
} else {
|
|
184
|
+
nextEntry()
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
nextEntry()
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
nextEntry()
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
131
196
|
function createDuplexFileStream (opts) {
|
|
132
197
|
opts = opts || {}
|
|
133
198
|
opts.readdirp = opts.readdirp || {}
|
|
@@ -156,32 +221,19 @@ function createDuplexFileStream (opts) {
|
|
|
156
221
|
cb()
|
|
157
222
|
})
|
|
158
223
|
} else if (stats.isDirectory()) {
|
|
159
|
-
var
|
|
160
|
-
var
|
|
161
|
-
var
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
173
|
-
.on('end', function () {
|
|
174
|
-
complete++
|
|
175
|
-
if (total == complete && readdirpComplete) cb()
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
.on('error', function (er) {
|
|
179
|
-
console.error('Directory error', filename, er)
|
|
180
|
-
cb(er)
|
|
181
|
-
})
|
|
182
|
-
.on('end', function () {
|
|
183
|
-
readdirpComplete = true
|
|
184
|
-
})
|
|
224
|
+
var walkOpts = Object.assign({}, DEFAULT_WALK_OPTS, opts.readdirp)
|
|
225
|
+
var fileFilter = createFilter(walkOpts.fileFilter)
|
|
226
|
+
var dirFilter = createFilter(walkOpts.directoryFilter)
|
|
227
|
+
|
|
228
|
+
walkDirectorySorted(filename, fileFilter, dirFilter, function (filePath, done) {
|
|
229
|
+
getText(filePath, opts)
|
|
230
|
+
.on('data', function (entry) { self.push(entry) })
|
|
231
|
+
.on('error', function (er) {
|
|
232
|
+
console.error('Directory getText error', filePath, er)
|
|
233
|
+
cb(er)
|
|
234
|
+
})
|
|
235
|
+
.on('end', done)
|
|
236
|
+
}, cb)
|
|
185
237
|
}
|
|
186
238
|
})
|
|
187
239
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tim-greller/xgettext-regex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Minimum viable xgettext .po file generator. Uses a configurable regex to get translation keys.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,11 +11,9 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"minimist": "^1.1.0",
|
|
13
13
|
"once": "^1.3.1",
|
|
14
|
-
"readdirp": "^3.1.3",
|
|
15
14
|
"split": "^1.0.1",
|
|
16
15
|
"stream-combiner": "^0.2.1",
|
|
17
|
-
"through2": "^3.0.1"
|
|
18
|
-
"xtend": "^4.0.0"
|
|
16
|
+
"through2": "^3.0.1"
|
|
19
17
|
},
|
|
20
18
|
"bin": {
|
|
21
19
|
"xgettext-regex": "bin/xgettext-regex.js"
|
package/test/test.js
CHANGED
|
@@ -164,4 +164,20 @@ test('Can create .pot from multiple files/directories', function (t) {
|
|
|
164
164
|
t.end()
|
|
165
165
|
}))
|
|
166
166
|
})
|
|
167
|
-
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('Emits directory entries in deterministic path order', function (t) {
|
|
170
|
+
t.plan(3)
|
|
171
|
+
|
|
172
|
+
xgettext.createReadStream(__dirname + '/fixtures')
|
|
173
|
+
.pipe(concat({encoding: 'string'}, function (pot) {
|
|
174
|
+
var indexJade = pot.indexOf('msgid "index.js"')
|
|
175
|
+
var indexPhp = pot.indexOf('msgid "php hypertext preprocessor"')
|
|
176
|
+
var jsApp = pot.indexOf('msgid "app.js"')
|
|
177
|
+
|
|
178
|
+
t.ok(indexJade > -1, 'index.jade translation exists')
|
|
179
|
+
t.ok(indexPhp > indexJade, 'index.php content appears after index.jade content')
|
|
180
|
+
t.ok(jsApp > indexPhp, 'js/app.js content appears after index.php content')
|
|
181
|
+
t.end()
|
|
182
|
+
}))
|
|
183
|
+
})
|