Skip to content

Commit fb1079c

Browse files
committed
Added html encode option
1 parent 5739228 commit fb1079c

File tree

5 files changed

+102
-52
lines changed

5 files changed

+102
-52
lines changed

index.js

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var path = require('path');
66
var _ = require('lodash');
77
var flat = require('flat');
88
var gutil = require('gulp-util');
9+
var he = require('he');
910
var through = require('through2');
1011

1112

@@ -38,49 +39,67 @@ var defaults = {
3839
ignoreErrors: false,
3940
dryRun: false,
4041
includeOriginal: false,
41-
ignoreTokens: false
42+
ignoreTokens: false,
43+
encodeEntities: true
4244
};
4345

46+
/**
47+
* A helper function to test whether an option was set to true or the value matches the options regular expression.
48+
* @param {boolean|string|RegExp} needle
49+
* @param {String} haystack
50+
* @returns {boolean}
51+
*/
52+
function trueOrMatch(needle, haystack) {
53+
if (needle === true) {
54+
return true;
55+
}
56+
if (needle instanceof RegExp && needle.test(haystack)) {
57+
return true;
58+
}
59+
return !!(needle instanceof String && haystack.indexOf(needle) !== -1);
60+
61+
}
62+
63+
4464
/**
4565
* Loads the dictionaries from the locale directory.
4666
* @param {Object} options
4767
*/
4868
function load(options) {
4969
if (cache[options.locales]) {
50-
dictionaries = cache[options.locales];
51-
} else {
52-
try {
53-
var files = fs.readdirSync(options.locales);
54-
for (var i in files) {
55-
var file = files[i];
56-
switch (path.extname(file)) {
57-
case '.json':
58-
case '.js':
59-
dictionaries[path.basename(file, path.extname(file))] = flat(require(path.join(process.cwd(), options.locales, file)));
60-
break;
61-
case '.ini':
62-
var iniData = fs.readFileSync(path.join(process.cwd(), options.locales, file));
63-
dictionaries[path.basename(file, path.extname(file))] = flat(ini2json(iniData));
64-
break;
65-
case '.csv':
66-
var csvData = fs.readFileSync(path.join(process.cwd(), options.locales, file));
67-
dictionaries[path.basename(file, path.extname(file))] = csv2json(csvData);
68-
break;
69-
}
70-
}
71-
if (options.cache) {
72-
cache[options.locales] = dictionaries;
70+
return dictionaries = cache[options.locales];
71+
}
72+
try {
73+
var files = fs.readdirSync(options.locales);
74+
for (var i in files) {
75+
var file = files[i];
76+
switch (path.extname(file)) {
77+
case '.json':
78+
case '.js':
79+
dictionaries[path.basename(file, path.extname(file))] = flat(require(path.join(process.cwd(), options.locales, file)));
80+
break;
81+
case '.ini':
82+
var iniData = fs.readFileSync(path.join(process.cwd(), options.locales, file));
83+
dictionaries[path.basename(file, path.extname(file))] = flat(ini2json(iniData));
84+
break;
85+
case '.csv':
86+
var csvData = fs.readFileSync(path.join(process.cwd(), options.locales, file));
87+
dictionaries[path.basename(file, path.extname(file))] = csv2json(csvData);
88+
break;
7389
}
74-
} catch (e) {
75-
throw new Error('No translation dictionaries have been found!');
7690
}
91+
if (options.cache) {
92+
cache[options.locales] = dictionaries;
93+
}
94+
} catch (e) {
95+
throw new Error('No translation dictionaries have been found!');
7796
}
7897
}
7998

8099
/**
81100
* Splits a line from an ini file into 2. Any subsequent '=' are ignored.
82-
* @param {string} line
83-
* @returns {string[]}
101+
* @param {String} line
102+
* @returns {String[]}
84103
*/
85104
function splitIniLine(line) {
86105
var separator = line.indexOf('=');
@@ -95,7 +114,7 @@ function splitIniLine(line) {
95114

96115
/**
97116
* Simple conversion helper to get a json file from an ini file.
98-
* @param {string} iniData
117+
* @param {String} iniData
99118
* @returns {{}}
100119
*/
101120
function ini2json(iniData) {
@@ -127,8 +146,8 @@ function ini2json(iniData) {
127146

128147
/**
129148
* Converts a line of a CSV file to an array of strings, omitting empty fields.
130-
* @param {string} line
131-
* @returns {string[]}
149+
* @param {String} line
150+
* @returns {String[]}
132151
*/
133152
function splitCsvLine(line) {
134153
if (!line.trim().length) {
@@ -163,7 +182,7 @@ function splitCsvLine(line) {
163182

164183
/**
165184
* Simple conversion helper to get a json file from a csv file.
166-
* @param {string} csvData
185+
* @param {String} csvData
167186
* @returns {Object}
168187
*/
169188
function csv2json(csvData) {
@@ -187,9 +206,9 @@ function csv2json(csvData) {
187206
/**
188207
* Performs the actual translation from a tokenized source to the final content.
189208
* @param {Object} options
190-
* @param {string} contents
209+
* @param {String} contents
191210
* @param {number} copied
192-
* @param {string} filePath
211+
* @param {String} filePath
193212
* @returns {Object}
194213
*/
195214
function translate(options, contents, copied, filePath) {
@@ -205,7 +224,7 @@ function translate(options, contents, copied, filePath) {
205224
throw new Error('No translation dictionaries available to create any files!');
206225
}
207226
var i = contents.indexOf(options.delimiter.prefix);
208-
if (!(options.ignoreTokens === true || options.ignoreTokens instanceof RegExp && options.ignoreTokens.test(filePath))) {
227+
if (!trueOrMatch(options.ignoreTokens, filePath)) {
209228
while ((i !== -1)) {
210229
var endMatch, length, token, key;
211230
var tail = contents.substr(i);
@@ -226,8 +245,12 @@ function translate(options, contents, copied, filePath) {
226245
for (var lang in processed) {
227246
processed[lang] += contents.substring(copied, i);
228247
if (dictionaries[lang][key] !== undefined) {
229-
processed[lang] += dictionaries[lang][key];
230-
} else if (options.warn) {
248+
if (trueOrMatch(options.encodeEntities, filePath)) {
249+
processed[lang] += he.encode(dictionaries[lang][key], { useNamedReferences: true });
250+
} else {
251+
processed[lang] += dictionaries[lang][key];
252+
}
253+
} else if (trueOrMatch(options.warn, filePath)) {
231254
gutil.log('Missing translation of language', lang, 'for key', key, 'in file', filePath);
232255
}
233256
processed[lang] += contents.substring(i + length, next == -1 ? contents.length : next);
@@ -315,18 +338,18 @@ module.exports = function(options) {
315338

316339
try {
317340
var files = replace(file, options);
318-
if (options.dryRun === true || options.dryRun instanceof RegExp && options.dryRun.test(file.path)) {
341+
if (trueOrMatch(options.dryRun, file.path)) {
319342
this.push(file);
320343
} else {
321-
if (options.includeOriginal === true || options.includeOriginal instanceof RegExp && options.includeOriginal.test(file.path)) {
344+
if (trueOrMatch(options.includeOriginal, file.path)) {
322345
this.push(file);
323346
}
324347
for (var i in files) {
325348
this.push(files[i]);
326349
}
327350
}
328351
} catch (err) {
329-
if (!options.ignoreErrors) {
352+
if (!trueOrMatch(options.ignoreErrors, file.path)) {
330353
this.emit('error', new gutil.PluginError('gulp-international', err));
331354
}
332355
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gulp-international",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "A gulp plugin that creates multi language versions of your source files",
55
"license": "Apache-2.0",
66
"homepage": "http://github.com/mallocator/gulp-international",
@@ -46,6 +46,7 @@
4646
"dependencies": {
4747
"flat": "^2.0.0",
4848
"gulp-util": "^3.0.1",
49+
"he": "^0.5.0",
4950
"lodash": "^4.6.1",
5051
"through2": "^2.0.1"
5152
},

readme.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,23 @@ The opposite of the whitelist. Any language specified here will be ignored durin
155155
with a string or multiple with an array of strings.
156156

157157

158+
### encodeEntities
159+
160+
Type: boolean|RegExp|String
161+
Default: ```true```
162+
163+
Any non utf8 characters that do not map to html are replaced with html entities if this option is enabled. A translation such as "über"
164+
would be replaced with "über". If the setting is a regular expression then only files with a matching (original) filename will be
165+
escaped. If the option is instead a string then the filename is searched for this substring.
166+
167+
158168
### warn
159169

160-
Type: boolean
170+
Type: boolean|RegExp|String
161171
Default: ```true```
162172

163-
This enables warnings to be printed out if any tokens are missing.
173+
This enables warnings to be printed out if any tokens are missing. If the setting is a regular expression then only files with a matching
174+
(original) filename will throw warnings. If the option is instead a string then the filename is searched for this substring.
164175

165176

166177
### cache
@@ -174,20 +185,22 @@ on your configuration you might want to disable caching based on how your livere
174185

175186
### ignoreErrors
176187

177-
Type: boolean
188+
Type: boolean|RegExp|String
178189
Default: ```false```
179190

180-
Allows to disable throwing of any errors that might occur so that the pipe will continue executing.
191+
Allows to disable throwing of any errors that might occur so that the pipe will continue executing. If the setting is a regular expression
192+
then only files with a matching (original) filename will ignore errors. If the option is instead a string then the filename is searched for
193+
this substring.
181194

182195

183196
### dryRun
184197

185-
Type: boolean|RegExp
198+
Type: boolean|RegExp|String
186199
Default: ```false```
187200

188201
When set to true the plugin will perform all operations for a translation, but will pass on the original file along the pipe instead
189202
of the newly generated ones. If the setting is a regular expression then only files with a matching (original) filename will be
190-
ignored.
203+
ignored. If the option is instead a string then the filename is searched for this substring.
191204

192205
Flow graph:
193206
```
@@ -202,7 +215,7 @@ Default: ```false```
202215

203216
When set to true the plugin will ignore all tokens, but still create new files as if they were different for each language. This
204217
differs from a dryRun, which would instead pass on the original file. If the setting is a regular expression then only files
205-
with a matching (original) filename will be ignored.
218+
with a matching (original) filename will be ignored. If the option is instead a string then the filename is searched for this substring.
206219

207220
Flow graph:
208221
```
@@ -215,8 +228,9 @@ source.file -> source-lang1.file -> original content
215228
Type: boolean|RegExp
216229
Default: ```false```
217230

218-
When set to true the original file is passed along the pipe along with all translated files. If the setting is a
219-
regular expression then only files with a matching (original) filename will be included.
231+
When set to true the original file is passed along the pipe along with all translated files. If the setting is a regular expression then
232+
only files with a matching (original) filename will be included. If the option is instead a string then the filename is searched for this
233+
substring.
220234

221235
Flow graph:
222236
```

test/index.test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('gulp-international', () => {
5353

5454

5555
it('should not process languages on the blacklist', done => {
56-
var options = { locales: 'test/locales', blacklist: 'en_US' };
56+
var options = { locales: 'test/locales', blacklist: 'en_US', encodeEntities: false };
5757
helper(options, files => {
5858
expect(files.length).to.equal(3);
5959
expect(files[0].contents.toString('utf8')).to.equal('<html><body><h1>Inhalt1</h1></body></html>');
@@ -173,6 +173,16 @@ describe('gulp-international', () => {
173173
done();
174174
});
175175
});
176+
177+
178+
it('should translate html entities if the option is enabled (default)', done => {
179+
var content = '<html><body><h1>R.entity</h1></body></html>';
180+
var options = { locales: 'test/locales', whitelist: 'de_DE' };
181+
helper(options, content, files => {
182+
expect(files[0].contents.toString()).to.equal('<html><body><h1>S&uuml;p&auml;r Sp&auml;&szlig;&ouml;!</h1></body></html>');
183+
done();
184+
});
185+
});
176186
});
177187

178188
describe('Error cases', () => {
@@ -345,7 +355,8 @@ describe('gulp-international', () => {
345355
`;
346356
var options = {
347357
locales: 'test/locales',
348-
whitelist: ['en_US', 'pt-BR']
358+
whitelist: ['en_US', 'pt-BR'],
359+
encodeEntities: false
349360
};
350361
helper(options, content, files => {
351362
expect(files.length).to.equal(2);

test/locales/de_DE.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
token1=Inhalt1
2+
entity=Süpär Späßö!
23

34
[section1]
45
token2=Inhalt2

0 commit comments

Comments
 (0)