Skip to content

Commit

Permalink
Merge pull request #168 from Project-OSRM/fallback
Browse files Browse the repository at this point in the history
Add fallback code for unsupported locales
  • Loading branch information
1ec5 committed Oct 5, 2017
2 parents 89a6184 + d32fbc9 commit 7de0b6c
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"lines-around-comment": "error",
"max-depth": "error",
"max-len": "off",
"max-lines": "error",
"max-lines": "off",
"max-nested-callbacks": "error",
"max-params": "error",
"max-statements": "off",
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log
All notable changes to this project will be documented in this file. For change log formatting, see http://keepachangelog.com/

## Master

- 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)

## 0.8.0 2017-10-04

- Added grammatical cases support for Russian way names [#102](https://github.com/Project-OSRM/osrm-text-instructions/pull/102)
Expand Down
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Grammatical cases and other translated strings customization after [Transifex](h
var version = 'v5';
var osrmTextInstructions = require('osrm-text-instructions')(version);

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

var language = 'en';
response.legs.forEach(function(leg) {
leg.steps.forEach(function(step) {
instruction = osrmTextInstructions.compile(language, step, options)
Expand Down
44 changes: 44 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ module.exports = function(version, _options) {
getWayName: function(language, step, options) {
var classes = options ? options.classes || [] : [];
if (typeof step !== 'object') throw new Error('step must be an Object');
if (!language) throw new Error('No language code provided');
if (!Array.isArray(classes)) throw new Error('classes must be an Array or undefined');

var wayName;
Expand Down Expand Up @@ -206,6 +207,7 @@ module.exports = function(version, _options) {
return this.tokenize(language, instruction, replaceTokens);
},
grammarize: function(language, name, grammar) {
if (!language) throw new Error('No language code provided');
// Process way/rotary name with applying grammar rules if any
if (name && grammar && grammars && grammars[language] && grammars[language][version]) {
var rules = grammars[language][version][grammar];
Expand All @@ -225,6 +227,7 @@ module.exports = function(version, _options) {
return name;
},
tokenize: function(language, instruction, tokens) {
if (!language) throw new Error('No language code provided');
// Keep this function context to use in inline function below (no arrow functions in ES4)
var that = this;
var output = instruction.replace(/\{(\w+):?(\w+)?\}/g, function(token, tag, grammar) {
Expand All @@ -243,6 +246,47 @@ module.exports = function(version, _options) {
}

return output;
},
getBestMatchingLanguage: function(language) {
if (languages.instructions[language]) return language;

var codes = languages.parseLanguageIntoCodes(language);
var languageCode = codes.language;
var scriptCode = codes.script;
var regionCode = codes.region;

// Same language code and script code (lng-Scpt)
if (languages.instructions[languageCode + '-' + scriptCode]) {
return languageCode + '-' + scriptCode;
}

// Same language code and region code (lng-CC)
if (languages.instructions[languageCode + '-' + regionCode]) {
return languageCode + '-' + regionCode;
}

// Same language code (lng)
if (languages.instructions[languageCode]) {
return languageCode;
}

// Same language code and any script code (lng-Scpx) and the found language contains a script
var anyScript = languages.parsedSupportedCodes.find(function (language) {
return language.language === languageCode && language.script;
});
if (anyScript) {
return anyScript.locale;
}

// Same language code and any region code (lng-CX)
var anyCountry = languages.parsedSupportedCodes.find(function (language) {
return language.language === languageCode && language.region;
});
if (anyCountry) {
return anyCountry.locale;
}

return 'en';
}
};
};
30 changes: 29 additions & 1 deletion languages.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,36 @@ var grammars = {
'ru': grammarRu
};

function parseLanguageIntoCodes (language) {
var match = language.match(/(\w\w)(?:-(\w\w\w\w))?(?:-(\w\w))?/i);
var locale = [];
if (match[1]) {
match[1] = match[1].toLowerCase();
locale.push(match[1]);
}
if (match[2]) {
match[2] = match[2][0].toUpperCase() + match[2].substring(1).toLowerCase();
locale.push(match[2]);
}
if (match[3]) {
match[3] = match[3].toUpperCase();
locale.push(match[3]);
}

return {
locale: locale.join('-'),
language: match[1],
script: match[2],
region: match[3]
};
}

module.exports = {
supportedCodes: Object.keys(instructions),
parsedSupportedCodes: Object.keys(instructions).map(function(language) {
return parseLanguageIntoCodes(language);
}),
instructions: instructions,
grammars: grammars
grammars: grammars,
parseLanguageIntoCodes: parseLanguageIntoCodes
};
93 changes: 91 additions & 2 deletions test/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,101 @@ tape.test('v5 compile', function(t) {

assert.throws(function() {
v5Compiler.compile('foo');
}, /language code foo not loaded/
);
}, /language code foo not loaded/);

assert.end();
});

t.test('en-US fallback to en', function(assert) {
var v5Compiler = compiler('v5');
var language = v5Compiler.getBestMatchingLanguage('en-us');

assert.equal(v5Compiler.compile(language, {
maneuver: {
type: 'turn',
modifier: 'left'
},
name: 'Way Name'
}), 'Turn left onto Way Name');

assert.end();
});

t.test('zh-CN fallback to zh-Hans', function(assert) {
var v5Compiler = compiler('v5');
var language = v5Compiler.getBestMatchingLanguage('zh-CN');

assert.equal(v5Compiler.compile(language, {
maneuver: {
type: 'turn',
modifier: 'left'
},
name: 'Way Name'
}), '左转,上Way Name');

assert.end();
});

t.test('zh-Hant fallback to zh-Hanz', function(assert) {
var v5Compiler = compiler('v5');
var language = v5Compiler.getBestMatchingLanguage('zh-Hant');

assert.equal(v5Compiler.compile(language, {
maneuver: {
type: 'turn',
modifier: 'left'
},
name: 'Way Name'
}), '左转,上Way Name');

assert.end();
});

t.test('zh-Hant-TW fallback to zh-Hant', function(assert) {
var v5Compiler = compiler('v5');
var language = v5Compiler.getBestMatchingLanguage('zh-Hant-TW');

assert.equal(v5Compiler.compile(language, {
maneuver: {
type: 'turn',
modifier: 'left'
},
name: 'Way Name'
}), '左转,上Way Name');

assert.end();
});

t.test('es-MX fallback to es', function(assert) {
var v5Compiler = compiler('v5');
var language = v5Compiler.getBestMatchingLanguage('es-MX');

assert.equal(v5Compiler.compile(language, {
maneuver: {
type: 'turn',
modifier: 'straight'
},
name: 'Way Name'
}), 'Ve recto en Way Name');

assert.end();
});

t.test('getBestMatchingLanguage', function(t) {
t.equal(compiler('v5').getBestMatchingLanguage('foo'), 'en');
t.equal(compiler('v5').getBestMatchingLanguage('en-US'), 'en');
t.equal(compiler('v5').getBestMatchingLanguage('zh-CN'), 'zh-Hans');
t.equal(compiler('v5').getBestMatchingLanguage('zh-Hant'), 'zh-Hans');
t.equal(compiler('v5').getBestMatchingLanguage('zh-Hant-TW'), 'zh-Hans');
t.equal(compiler('v5').getBestMatchingLanguage('zh'), 'zh-Hans');
t.equal(compiler('v5').getBestMatchingLanguage('es-MX'), 'es');
t.equal(compiler('v5').getBestMatchingLanguage('es-ES'), 'es-ES');
t.equal(compiler('v5').getBestMatchingLanguage('pt-PT'), 'pt-BR');
t.equal(compiler('v5').getBestMatchingLanguage('pt'), 'pt-BR');
t.equal(compiler('v5').getBestMatchingLanguage('pt-pt'), 'pt-BR');
t.end();
});

t.test('respects options.instructionStringHook', function(assert) {
var v5Compiler = compiler('v5', {
hooks: {
Expand Down
16 changes: 16 additions & 0 deletions test/languages_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ tape.test('verify language files structure', function(assert) {

assert.end();
});

/* eslint-disable */
tape.test('parseLanguageIntoCodes', function(t) {
t.deepEqual(languages.parseLanguageIntoCodes('foo'), { region: undefined, language: 'fo', locale: 'fo', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('en-US'), { region: 'US', language: 'en', locale: 'en-US', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('zh-CN'), { region: 'CN', language: 'zh', locale: 'zh-CN', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('zh-Hant'), { region: undefined, language: 'zh', locale: 'zh-Hant', script: 'Hant' });
t.deepEqual(languages.parseLanguageIntoCodes('zh-Hant-TW'), { region: 'TW', language: 'zh', locale: 'zh-Hant-TW', script: 'Hant' });
t.deepEqual(languages.parseLanguageIntoCodes('zh'), { region: undefined, language: 'zh', locale: 'zh', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('es-MX'), { region: 'MX', language: 'es', locale: 'es-MX', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('es-ES'), { region: 'ES', language: 'es', locale: 'es-ES', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('pt-PT'), { region: 'PT', language: 'pt', locale: 'pt-PT', script: undefined });
t.deepEqual(languages.parseLanguageIntoCodes('pt'), { region: undefined, language: 'pt', locale: 'pt', script: undefined });
t.end();
});
/* eslint-enable */

0 comments on commit 7de0b6c

Please sign in to comment.