Bug 730209: Parse spellchecker dictionary names as BCP 47 language tags. r=gavin

This commit is contained in:
Gordon P. Hemsley 2012-03-28 18:56:02 -04:00
parent 720215548d
commit 809beca2af
3 changed files with 121 additions and 33 deletions

View File

@ -183,16 +183,6 @@ InlineSpellChecker.prototype = {
this.mDictionaryNames = [];
this.mDictionaryItems = [];
if (! gLanguageBundle) {
// create the bundles for language and region
var bundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
gLanguageBundle = bundleService.createBundle(
"chrome://global/locale/languageNames.properties");
gRegionBundle = bundleService.createBundle(
"chrome://global/locale/regionNames.properties");
}
if (! this.mInlineSpellChecker || ! this.enabled)
return 0;
var spellchecker = this.mInlineSpellChecker.spellChecker;
@ -204,33 +194,12 @@ InlineSpellChecker.prototype = {
try {
curlang = spellchecker.GetCurrentDictionary();
} catch(e) {}
var isoStrArray;
for (var i = 0; i < list.length; i ++) {
// get the display name for this dictionary
isoStrArray = list[i].split(/[-_]/);
var displayName = "";
if (gLanguageBundle && isoStrArray[0]) {
try {
displayName = gLanguageBundle.GetStringFromName(isoStrArray[0].toLowerCase());
} catch(e) {} // ignore language bundle errors
if (gRegionBundle && isoStrArray[1]) {
try {
displayName += " / " + gRegionBundle.GetStringFromName(isoStrArray[1].toLowerCase());
} catch(e) {} // ignore region bundle errors
if (isoStrArray[2])
displayName += " (" + isoStrArray[2] + ")";
}
}
// if we didn't get a name, just use the raw dictionary name
if (displayName.length == 0)
displayName = list[i];
this.mDictionaryNames.push(list[i]);
var item = menu.ownerDocument.createElement("menuitem");
item.setAttribute("id", "spell-check-dictionary-" + list[i]);
item.setAttribute("label", displayName);
item.setAttribute("label", this.getDictionaryDisplayName(list[i]));
item.setAttribute("type", "radio");
this.mDictionaryItems.push(item);
if (curlang == list[i]) {
@ -247,6 +216,65 @@ InlineSpellChecker.prototype = {
return list.length;
},
// Formats a valid BCP 47 language tag based on available localized names.
getDictionaryDisplayName: function(dictionaryName) {
try {
// Get the display name for this dictionary.
let languageTagMatch = /^([a-z]{2,3}|[a-z]{4}|[a-z]{5,8})(?:[-_]([a-z]{4}))?(?:[-_]([A-Z]{2}|[0-9]{3}))?((?:[-_](?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*)$/i;
var [languageTag, languageSubtag, scriptSubtag, regionSubtag, variantSubtags] = dictionaryName.match(languageTagMatch);
} catch(e) {
// If we weren't given a valid language tag, just use the raw dictionary name.
return dictionaryName;
}
if (!gLanguageBundle) {
// Create the bundles for language and region names.
var bundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
gLanguageBundle = bundleService.createBundle(
"chrome://global/locale/languageNames.properties");
gRegionBundle = bundleService.createBundle(
"chrome://global/locale/regionNames.properties");
}
var displayName = "";
// Language subtag will normally be 2 or 3 letters, but could be up to 8.
try {
displayName += gLanguageBundle.GetStringFromName(languageSubtag.toLowerCase());
} catch(e) {
displayName += languageSubtag.toLowerCase(); // Fall back to raw language subtag.
}
// Region subtag will be 2 letters or 3 digits.
if (regionSubtag) {
displayName += " (";
try {
displayName += gRegionBundle.GetStringFromName(regionSubtag.toLowerCase());
} catch(e) {
displayName += regionSubtag.toUpperCase(); // Fall back to raw region subtag.
}
displayName += ")";
}
// Script subtag will be 4 letters.
if (scriptSubtag) {
displayName += " / ";
// XXX: See bug 666662 and bug 666731 for full implementation.
displayName += scriptSubtag; // Fall back to raw script subtag.
}
// Each variant subtag will be 4 to 8 chars.
if (variantSubtags)
// XXX: See bug 666662 and bug 666731 for full implementation.
displayName += " (" + variantSubtags.substr(1).split(/[-_]/).join(" / ") + ")"; // Collapse multiple variants.
return displayName;
},
// undoes the work of addDictionaryListToMenu for the menu
// (call on popup hiding)
clearDictionaryListFromMenu: function()
@ -290,7 +318,7 @@ InlineSpellChecker.prototype = {
// Prevent the undo stack from growing over the max depth
if (this.mAddedWordStack.length == MAX_UNDO_STACK_DEPTH)
this.mAddedWordStack.shift();
this.mAddedWordStack.push(this.mMisspelling);
this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
},

View File

@ -55,6 +55,7 @@ _BROWSER_TEST_FILES = \
browser_bug295977_autoscroll_overflow.js \
browser_bug594509.js \
browser_Geometry.js \
browser_InlineSpellChecker.js \
browser_save_resend_postdata.js \
browser_browserDrop.js \
browser_Services.js \

View File

@ -0,0 +1,59 @@
function test() {
let tempScope = {};
Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm", tempScope);
let InlineSpellChecker = tempScope.InlineSpellChecker;
ok(InlineSpellChecker, "InlineSpellChecker class exists");
for (var fname in tests) {
tests[fname]();
}
}
let tests = {
// Test various possible dictionary name to ensure they display as expected.
// XXX: This only works for the 'en-US' locale, as the testing involves localized output.
testDictionaryDisplayNames: function() {
let isc = new InlineSpellChecker();
// Check for valid language tag.
is(isc.getDictionaryDisplayName("-invalid-"), "-invalid-", "'-invalid-' should display as '-invalid-'");
// Check if display name is available for language subtag.
is(isc.getDictionaryDisplayName("en"), "English", "'en' should display as 'English'");
is(isc.getDictionaryDisplayName("qaz"), "qaz", "'qaz' should display as 'qaz'"); // Private use subtag
// Check if display name is available for region subtag.
is(isc.getDictionaryDisplayName("en-US"), "English (United States)", "'en-US' should display as 'English (United States)'");
is(isc.getDictionaryDisplayName("en-QZ"), "English (QZ)", "'en-QZ' should display as 'English (QZ)'"); // Private use subtag
todo_is(isc.getDictionaryDisplayName("es-419"), "Spanish (Latin America and the Caribbean)", "'es-419' should display as 'Spanish (Latin America and the Caribbean)'");
// Check if display name is available for script subtag.
todo_is(isc.getDictionaryDisplayName("en-Cyrl"), "English / Cyrillic", "'en-Cyrl' should display as 'English / Cyrillic'");
todo_is(isc.getDictionaryDisplayName("en-Cyrl-US"), "English (United States) / Cyrillic", "'en-Cyrl-US' should display as 'English (United States) / Cyrillic'");
todo_is(isc.getDictionaryDisplayName("en-Cyrl-QZ"), "English (QZ) / Cyrillic", "'en-Cyrl-QZ' should display as 'English (QZ) / Cyrillic'"); // Private use subtag
todo_is(isc.getDictionaryDisplayName("qaz-Cyrl"), "qaz / Cyrillic", "'qaz-Cyrl' should display as 'qaz / Cyrillic'"); // Private use subtag
todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-US"), "qaz (United States) / Cyrillic", "'qaz-Cyrl-US' should display as 'qaz (United States) / Cyrillic'"); // Private use subtag
todo_is(isc.getDictionaryDisplayName("qaz-Cyrl-QZ"), "qaz (QZ) / Cyrillic", "'qaz-Cyrl-QZ' should display as 'qaz (QZ) / Cyrillic'"); // Private use subtags
is(isc.getDictionaryDisplayName("en-Qaaz"), "English / Qaaz", "'en-Qaaz' should display as 'English / Qaaz'"); // Private use subtag
is(isc.getDictionaryDisplayName("en-Qaaz-US"), "English (United States) / Qaaz", "'en-Qaaz-US' should display as 'English (United States) / Qaaz'"); // Private use subtag
is(isc.getDictionaryDisplayName("en-Qaaz-QZ"), "English (QZ) / Qaaz", "'en-Qaaz-QZ' should display as 'English (QZ) / Qaaz'"); // Private use subtags
is(isc.getDictionaryDisplayName("qaz-Qaaz"), "qaz / Qaaz", "'qaz-Qaaz' should display as 'qaz / Qaaz'"); // Private use subtags
is(isc.getDictionaryDisplayName("qaz-Qaaz-US"), "qaz (United States) / Qaaz", "'qaz-Qaaz-US' should display as 'qaz (United States) / Qaaz'"); // Private use subtags
is(isc.getDictionaryDisplayName("qaz-Qaaz-QZ"), "qaz (QZ) / Qaaz", "'qaz-Qaaz-QZ' should display as 'qaz (QZ) / Qaaz'"); // Private use subtags
// Check if display name is available for variant subtag.
// XXX: It isn't clear how we'd ideally want to display variant subtags.
is(isc.getDictionaryDisplayName("de-1996"), "German (1996)", "'de-1996' should display as 'German (1996)'");
is(isc.getDictionaryDisplayName("de-CH-1996"), "German (Switzerland) (1996)", "'de-CH-1996' should display as 'German (Switzerland) (1996)'");
// Complex cases.
// XXX: It isn't clear how we'd ideally want to display variant subtags.
todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa"), "English (United States) / Cyrillic (fonipa)", "'en-Cyrl-US-fonipa' should display as 'English (United States) / Cyrillic (fonipa)'");
todo_is(isc.getDictionaryDisplayName("en-Cyrl-US-fonipa-fonxsamp"), "English (United States) / Cyrillic (fonipa / fonxsamp)", "'en-Cyrl-US-fonipa-fonxsamp' should display as 'English (United States) / Cyrillic (fonipa / fonxsamp)'");
is(isc.getDictionaryDisplayName("qaz-Qaaz-QZ-fonipa"), "qaz (QZ) / Qaaz (fonipa)", "'qaz-Qaaz-QZ-fonipa' should display as 'qaz (QZ) / Qaaz (fonipa)'"); // Private use subtags
is(isc.getDictionaryDisplayName("qaz-Qaaz-QZ-fonipa-fonxsamp"), "qaz (QZ) / Qaaz (fonipa / fonxsamp)", "'qaz-Qaaz-QZ-fonipa-fonxsamp' should display as 'qaz (QZ) / Qaaz (fonipa / fonxsamp)'"); // Private use subtags
// Check if display name is available for grandfathered tags.
todo_is(isc.getDictionaryDisplayName("en-GB-oed"), "English (United Kingdom) (OED)", "'en-GB-oed' should display as 'English (United Kingdom) (OED)'");
},
};