Skip to content

Commit 7de0b6c

Browse files
authored
Merge pull request #168 from Project-OSRM/fallback
Add fallback code for unsupported locales
2 parents 89a6184 + d32fbc9 commit 7de0b6c

File tree

7 files changed

+187
-6
lines changed

7 files changed

+187
-6
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"lines-around-comment": "error",
8181
"max-depth": "error",
8282
"max-len": "off",
83-
"max-lines": "error",
83+
"max-lines": "off",
8484
"max-nested-callbacks": "error",
8585
"max-params": "error",
8686
"max-statements": "off",

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Change Log
22
All notable changes to this project will be documented in this file. For change log formatting, see http://keepachangelog.com/
33

4+
## Master
5+
6+
- Added `getBestMatchingLanguage` for determining the closest available language. Pass a user locale into this method before passing the return value into `compile`. [#168](https://github.com/Project-OSRM/osrm-text-instructions/pull/168)
7+
48
## 0.8.0 2017-10-04
59

610
- Added grammatical cases support for Russian way names [#102](https://github.com/Project-OSRM/osrm-text-instructions/pull/102)

Readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ Grammatical cases and other translated strings customization after [Transifex](h
2727
var version = 'v5';
2828
var osrmTextInstructions = require('osrm-text-instructions')(version);
2929

30-
// make your request against the API, save result to response variable
30+
// If you’re unsure if the user’s locale is supported, use `getBestMatchingLanguage` method to find an appropriate language.
31+
var language = osrmTextInstructions.getBestMatchingLanguage('en-US');
3132

32-
var language = 'en';
3333
response.legs.forEach(function(leg) {
3434
leg.steps.forEach(function(step) {
3535
instruction = osrmTextInstructions.compile(language, step, options)

index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ module.exports = function(version, _options) {
7272
getWayName: function(language, step, options) {
7373
var classes = options ? options.classes || [] : [];
7474
if (typeof step !== 'object') throw new Error('step must be an Object');
75+
if (!language) throw new Error('No language code provided');
7576
if (!Array.isArray(classes)) throw new Error('classes must be an Array or undefined');
7677

7778
var wayName;
@@ -206,6 +207,7 @@ module.exports = function(version, _options) {
206207
return this.tokenize(language, instruction, replaceTokens);
207208
},
208209
grammarize: function(language, name, grammar) {
210+
if (!language) throw new Error('No language code provided');
209211
// Process way/rotary name with applying grammar rules if any
210212
if (name && grammar && grammars && grammars[language] && grammars[language][version]) {
211213
var rules = grammars[language][version][grammar];
@@ -225,6 +227,7 @@ module.exports = function(version, _options) {
225227
return name;
226228
},
227229
tokenize: function(language, instruction, tokens) {
230+
if (!language) throw new Error('No language code provided');
228231
// Keep this function context to use in inline function below (no arrow functions in ES4)
229232
var that = this;
230233
var output = instruction.replace(/\{(\w+):?(\w+)?\}/g, function(token, tag, grammar) {
@@ -243,6 +246,47 @@ module.exports = function(version, _options) {
243246
}
244247

245248
return output;
249+
},
250+
getBestMatchingLanguage: function(language) {
251+
if (languages.instructions[language]) return language;
252+
253+
var codes = languages.parseLanguageIntoCodes(language);
254+
var languageCode = codes.language;
255+
var scriptCode = codes.script;
256+
var regionCode = codes.region;
257+
258+
// Same language code and script code (lng-Scpt)
259+
if (languages.instructions[languageCode + '-' + scriptCode]) {
260+
return languageCode + '-' + scriptCode;
261+
}
262+
263+
// Same language code and region code (lng-CC)
264+
if (languages.instructions[languageCode + '-' + regionCode]) {
265+
return languageCode + '-' + regionCode;
266+
}
267+
268+
// Same language code (lng)
269+
if (languages.instructions[languageCode]) {
270+
return languageCode;
271+
}
272+
273+
// Same language code and any script code (lng-Scpx) and the found language contains a script
274+
var anyScript = languages.parsedSupportedCodes.find(function (language) {
275+
return language.language === languageCode && language.script;
276+
});
277+
if (anyScript) {
278+
return anyScript.locale;
279+
}
280+
281+
// Same language code and any region code (lng-CX)
282+
var anyCountry = languages.parsedSupportedCodes.find(function (language) {
283+
return language.language === languageCode && language.region;
284+
});
285+
if (anyCountry) {
286+
return anyCountry.locale;
287+
}
288+
289+
return 'en';
246290
}
247291
};
248292
};

languages.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,36 @@ var grammars = {
4949
'ru': grammarRu
5050
};
5151

52+
function parseLanguageIntoCodes (language) {
53+
var match = language.match(/(\w\w)(?:-(\w\w\w\w))?(?:-(\w\w))?/i);
54+
var locale = [];
55+
if (match[1]) {
56+
match[1] = match[1].toLowerCase();
57+
locale.push(match[1]);
58+
}
59+
if (match[2]) {
60+
match[2] = match[2][0].toUpperCase() + match[2].substring(1).toLowerCase();
61+
locale.push(match[2]);
62+
}
63+
if (match[3]) {
64+
match[3] = match[3].toUpperCase();
65+
locale.push(match[3]);
66+
}
67+
68+
return {
69+
locale: locale.join('-'),
70+
language: match[1],
71+
script: match[2],
72+
region: match[3]
73+
};
74+
}
75+
5276
module.exports = {
5377
supportedCodes: Object.keys(instructions),
78+
parsedSupportedCodes: Object.keys(instructions).map(function(language) {
79+
return parseLanguageIntoCodes(language);
80+
}),
5481
instructions: instructions,
55-
grammars: grammars
82+
grammars: grammars,
83+
parseLanguageIntoCodes: parseLanguageIntoCodes
5684
};

test/index_test.js

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,101 @@ tape.test('v5 compile', function(t) {
190190

191191
assert.throws(function() {
192192
v5Compiler.compile('foo');
193-
}, /language code foo not loaded/
194-
);
193+
}, /language code foo not loaded/);
195194

196195
assert.end();
197196
});
198197

198+
t.test('en-US fallback to en', function(assert) {
199+
var v5Compiler = compiler('v5');
200+
var language = v5Compiler.getBestMatchingLanguage('en-us');
201+
202+
assert.equal(v5Compiler.compile(language, {
203+
maneuver: {
204+
type: 'turn',
205+
modifier: 'left'
206+
},
207+
name: 'Way Name'
208+
}), 'Turn left onto Way Name');
209+
210+
assert.end();
211+
});
212+
213+
t.test('zh-CN fallback to zh-Hans', function(assert) {
214+
var v5Compiler = compiler('v5');
215+
var language = v5Compiler.getBestMatchingLanguage('zh-CN');
216+
217+
assert.equal(v5Compiler.compile(language, {
218+
maneuver: {
219+
type: 'turn',
220+
modifier: 'left'
221+
},
222+
name: 'Way Name'
223+
}), '左转,上Way Name');
224+
225+
assert.end();
226+
});
227+
228+
t.test('zh-Hant fallback to zh-Hanz', function(assert) {
229+
var v5Compiler = compiler('v5');
230+
var language = v5Compiler.getBestMatchingLanguage('zh-Hant');
231+
232+
assert.equal(v5Compiler.compile(language, {
233+
maneuver: {
234+
type: 'turn',
235+
modifier: 'left'
236+
},
237+
name: 'Way Name'
238+
}), '左转,上Way Name');
239+
240+
assert.end();
241+
});
242+
243+
t.test('zh-Hant-TW fallback to zh-Hant', function(assert) {
244+
var v5Compiler = compiler('v5');
245+
var language = v5Compiler.getBestMatchingLanguage('zh-Hant-TW');
246+
247+
assert.equal(v5Compiler.compile(language, {
248+
maneuver: {
249+
type: 'turn',
250+
modifier: 'left'
251+
},
252+
name: 'Way Name'
253+
}), '左转,上Way Name');
254+
255+
assert.end();
256+
});
257+
258+
t.test('es-MX fallback to es', function(assert) {
259+
var v5Compiler = compiler('v5');
260+
var language = v5Compiler.getBestMatchingLanguage('es-MX');
261+
262+
assert.equal(v5Compiler.compile(language, {
263+
maneuver: {
264+
type: 'turn',
265+
modifier: 'straight'
266+
},
267+
name: 'Way Name'
268+
}), 'Ve recto en Way Name');
269+
270+
assert.end();
271+
});
272+
273+
t.test('getBestMatchingLanguage', function(t) {
274+
t.equal(compiler('v5').getBestMatchingLanguage('foo'), 'en');
275+
t.equal(compiler('v5').getBestMatchingLanguage('en-US'), 'en');
276+
t.equal(compiler('v5').getBestMatchingLanguage('zh-CN'), 'zh-Hans');
277+
t.equal(compiler('v5').getBestMatchingLanguage('zh-Hant'), 'zh-Hans');
278+
t.equal(compiler('v5').getBestMatchingLanguage('zh-Hant-TW'), 'zh-Hans');
279+
t.equal(compiler('v5').getBestMatchingLanguage('zh'), 'zh-Hans');
280+
t.equal(compiler('v5').getBestMatchingLanguage('es-MX'), 'es');
281+
t.equal(compiler('v5').getBestMatchingLanguage('es-ES'), 'es-ES');
282+
t.equal(compiler('v5').getBestMatchingLanguage('pt-PT'), 'pt-BR');
283+
t.equal(compiler('v5').getBestMatchingLanguage('pt'), 'pt-BR');
284+
t.equal(compiler('v5').getBestMatchingLanguage('pt-pt'), 'pt-BR');
285+
t.end();
286+
});
287+
199288
t.test('respects options.instructionStringHook', function(assert) {
200289
var v5Compiler = compiler('v5', {
201290
hooks: {

test/languages_test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,19 @@ tape.test('verify language files structure', function(assert) {
3535

3636
assert.end();
3737
});
38+
39+
/* eslint-disable */
40+
tape.test('parseLanguageIntoCodes', function(t) {
41+
t.deepEqual(languages.parseLanguageIntoCodes('foo'), { region: undefined, language: 'fo', locale: 'fo', script: undefined });
42+
t.deepEqual(languages.parseLanguageIntoCodes('en-US'), { region: 'US', language: 'en', locale: 'en-US', script: undefined });
43+
t.deepEqual(languages.parseLanguageIntoCodes('zh-CN'), { region: 'CN', language: 'zh', locale: 'zh-CN', script: undefined });
44+
t.deepEqual(languages.parseLanguageIntoCodes('zh-Hant'), { region: undefined, language: 'zh', locale: 'zh-Hant', script: 'Hant' });
45+
t.deepEqual(languages.parseLanguageIntoCodes('zh-Hant-TW'), { region: 'TW', language: 'zh', locale: 'zh-Hant-TW', script: 'Hant' });
46+
t.deepEqual(languages.parseLanguageIntoCodes('zh'), { region: undefined, language: 'zh', locale: 'zh', script: undefined });
47+
t.deepEqual(languages.parseLanguageIntoCodes('es-MX'), { region: 'MX', language: 'es', locale: 'es-MX', script: undefined });
48+
t.deepEqual(languages.parseLanguageIntoCodes('es-ES'), { region: 'ES', language: 'es', locale: 'es-ES', script: undefined });
49+
t.deepEqual(languages.parseLanguageIntoCodes('pt-PT'), { region: 'PT', language: 'pt', locale: 'pt-PT', script: undefined });
50+
t.deepEqual(languages.parseLanguageIntoCodes('pt'), { region: undefined, language: 'pt', locale: 'pt', script: undefined });
51+
t.end();
52+
});
53+
/* eslint-enable */

0 commit comments

Comments
 (0)