Skip to content

Commit

Permalink
Move Locale to its own file and rewrite it to follow the spec
Browse files Browse the repository at this point in the history
  • Loading branch information
Zibi Braniecki committed Oct 9, 2016
1 parent 6378108 commit ac8f37d
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 153 deletions.
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ let loc3 = new global.IntlPolyfill.Locale(x, {
numeric: false,
caseFirst: "false"
});

loc3.foo = 'foo';

console.log(loc3);
console.log(loc3.toString());
2 changes: 1 addition & 1 deletion src/11.numberformat.js
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ function ToRawFixed(x, minInteger, minFraction, maxFraction) {

// Sect 11.3.2 Table 2, Numbering systems
// ======================================
let numSys = {
export let numSys = {
arab: ['\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668', '\u0669'],
arabext: ['\u06F0', '\u06F1', '\u06F2', '\u06F3', '\u06F4', '\u06F5', '\u06F6', '\u06F7', '\u06F8', '\u06F9'],
bali: ['\u1B50', '\u1B51', '\u1B52', '\u1B53', '\u1B54', '\u1B55', '\u1B56', '\u1B57', '\u1B58', '\u1B59'],
Expand Down
157 changes: 157 additions & 0 deletions src/14.locale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
Intl
} from "./8.intl.js";

import {
UnicodeExtensionSubtags,
expUnicodeExSeq
} from "./9.negotiation.js";

import {
IsStructurallyValidLanguageTag,
CanonicalizeLanguageTag,
toLatinUpperCase,
IsWellFormedCurrencyCode
} from "./6.locales-currencies-tz.js";

import {
numSys
} from "./11.numberformat.js";

import {
defineProperty,
Record
} from "./util.js";

const keyToOptionMap = {
'ca': 'calendar',
'co': 'collation',
'cu': 'currency',
'hc': 'hourCycle',
'kf': 'caseFirst',
'kn': 'numeric',
'nu': 'numberingSystem',
'tz': 'timeZone'
};

const optionToKeyMap = {};
for (let key in keyToOptionMap) {
let option = keyToOptionMap[key];
optionToKeyMap[option] = key;
}

function DeconstructLanguageTag(locale) {
let result = new Record();

let noExtensionsLoc = String(locale).replace(expUnicodeExSeq, '');
let extension = locale.match(expUnicodeExSeq)[0];
result['[[Locale]]'] = noExtensionsLoc;
result['[[Extension]]'] = extension;

return result;
}

function IsOptionValueSupported(option, value) {
switch (option) {
case 'calendar':
//XXX: Get the list of available calendars
return true;
case 'collation':
//XXX: Get the list of available collations
return true;
case 'currency':
return IsWellFormedCurrencyCode(value);
case 'hourCycle':
return ['h12', 'h24'].includes(value);
case 'caseFirst':
return ['upper', 'lower', false].includes(value);
case 'numeric':
return typeof value === 'boolean';
case 'numberingSystem':
return numSys.hasOwnProperty(value);
case 'timeZone':
//XXX: accept more time zones
return toLatinUpperCase(value) === 'UTC';
}
throw new RangeError(`Unknown option ${option}`);
}

class Locale {
constructor(tag, options = {}) {
if (!IsStructurallyValidLanguageTag(tag)) {
throw new RangeError(`${tag} is not a structurally valid language tag`);
}
let locale = this;
let canonicalizedTag = CanonicalizeLanguageTag(tag);
let r = DeconstructLanguageTag(canonicalizedTag);
this.locale = r['[[Locale]]'];

let optionKeys = Object.keys(options);
for (let key in optionKeys) {
if (optionToKeyMap.hasOwnProperty(key)) {
let value = options[key];
if (!IsOptionValueSupported(key, value)) {
throw new RangeError(`Invalid value ${value} for option ${key}`);
}
locale[key] = value;
}
}
if (r['[[Extension]]']) {
let unicodeSubtags = UnicodeExtensionSubtags(r['[[Extension]]']);
let i = 0;
let len = unicodeSubtags.length;
while (i < len) {
let key = unicodeSubtags[i];
if (keyToOptionMap.hasOwnProperty(key)) {
i += 1;
let value = unicodeSubtags[i];

if (value === 'true') value = true;
if (value === 'false') value = false;
let name = keyToOptionMap[key];
if (!IsOptionValueSupported(name, value)) {
throw new RangeError(`Invalid value ${value} for option ${name}`);
}
if (!locale.hasOwnProperty(name)) {
locale[name] = value;
}
}
i += 1;
}
}
Object.freeze(locale);
}

toString() {
let loc = this;

if (typeof loc !== 'object') {
throw new TypeError();
}
let locale = loc.locale;
let unicodeExt = '';

for (let name of Object.keys(optionToKeyMap)) {
let key = optionToKeyMap[name];
if (loc.hasOwnProperty(name)) {
let value = loc[name];
let kvp = `${key}-${value}`;
if (unicodeExt.length) {
unicodeExt = `${unicodeExt}-`;
}
unicodeExt = `${unicodeExt}${kvp}`;
}
}
//XXX: Consider caching the value since the object is frozen
if (unicodeExt.length) {
return `${locale}-${unicodeExt}`;
}
return locale;
}
}

defineProperty(Intl, 'Locale', {
configurable: true,
writable: true,
value: Locale
});
152 changes: 1 addition & 151 deletions src/8.intl.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import {
CanonicalizeLocaleList,
UnicodeExtensionSubtags
CanonicalizeLocaleList
} from "./9.negotiation.js";

import {
IsStructurallyValidLanguageTag,
CanonicalizeLanguageTag
} from "./6.locales-currencies-tz.js";

// 8 The Intl Object
export const Intl = {};

Expand Down Expand Up @@ -40,147 +34,3 @@ Object.defineProperty(Intl, 'getCanonicalLocales', {
writable: true,
value: getCanonicalLocales
});

const optionValues = {
calendar: {
values: ['buddhist', 'chinese', 'coptic', 'ethioaa', 'ethiopic', 'gregory', 'hebrew', 'indian', 'islamic', 'islamicc', 'iso8601', 'japanese', 'persian', 'roc'],
key: 'ca'
},
collation: {
values: ["big5han", "dict", "direct", "ducet", "gb2312", "phonebk", "phonetic", "pinyin", "reformed", "searchjl", "stroke", "trad", "unihan", "standard", "search"],
key: 'co'
},
currency: {
values: ["eur", "usd", "pln"],
key: 'cu'
},
hourCycle: {
values: ['h12', 'h24'],
key: 'hc'
},
caseFirst: {
values: ["upper", "lower", "false"],
key: 'kf'
},
numeric: {
values: ["true", "false"],
key: 'kn'
},
numberingSystem: {
values: ["arab", "arabext", "bali", "beng", "deva", "fullwide", "gujr", "guru", "hanidec", "khmr", "knda", "laoo", "latn", "limb", "mlym", "mong", "mymr", "orya", "tamldec", "telu", "thai", "tibt"],
key: 'nu'
},
timeZone: {
key: 'tz'
}
};

const keyToOptionMap = {
'ca': 'calendar',
'co': 'collation',
'cu': 'currency',
'hc': 'hourCycle',
'kf': 'caseFirst',
'kn': 'numeric',
'nu': 'numberingSystem',
'tz': 'timeZone'
};


const expExSeq = /-([xtu])(?:-[0-9a-z]{2,8})+/gi;

function deconstructLocaleString(locale) {
let res = {
locale: null,
extensions: {}
};

let r;
let extensionIndex;
while ((r = expExSeq.exec(locale)) !== null) {
if (extensionIndex === undefined) {
extensionIndex = r.index;
}
res.extensions[r[1]] = r[0];
}
if (extensionIndex !== undefined) {
res.locale = locale.substring(0, extensionIndex);
} else {
res.locale = locale;
}
return res;
}

class Locale {
constructor(locale, options = {}) {
if (!IsStructurallyValidLanguageTag(locale)) {
throw new RangeError(`${locale} is not a structurally valid language tag`);
}
let loc = CanonicalizeLanguageTag(locale);
let res = deconstructLocaleString(loc);
this.locale = res.locale;

for (let name in options) {
if (!optionValues.hasOwnProperty(name)) {
continue;
}

let value = options[name];

if (value === false) value = "false";
if (value === true) value = "true";

if (optionValues[name].hasOwnProperty('values') &&
!optionValues[name].values.includes(value)) {
throw new RangeError(`Invalid value ${value} for option ${name}`);
}
this[name] = options[name];
}

if (!res.extensions.hasOwnProperty('u')) {
return;
}
let extensionSubtags = UnicodeExtensionSubtags(res.extensions['u']);

for (let i = 0; i < extensionSubtags.length; i++) {
let key = extensionSubtags[i];
if (keyToOptionMap.hasOwnProperty(key)) {
let value = extensionSubtags[++i];

let name = keyToOptionMap[key];
if (this.hasOwnProperty(name)) {
continue;
}
if (optionValues[name].hasOwnProperty('values') &&
!optionValues[name].values.includes(value)) {
throw new RangeError(`Invalid value ${value} for key ${key}`);
}
this[name] = value;
}
}
Object.freeze(this);
}

toString() {
let exts = [];

for (let key in keyToOptionMap) {
let name = keyToOptionMap[key];
if (!this.hasOwnProperty(name)) {
continue;
}
exts.push(`${key}-${this[name]}`);
}
if (exts.length) {
return `${this.locale}-u-${exts.join('-')}`;
}
return this.locale;
}
}

Object.defineProperty(Intl, 'Locale', {
enumerable: false,
configurable: true,
writable: true,
value: Locale
});
2 changes: 1 addition & 1 deletion src/9.negotiation.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
DefaultLocale
} from "./6.locales-currencies-tz.js";

const expUnicodeExSeq = /-u(?:-[0-9a-z]{2,8})+/gi; // See `extension` below
export const expUnicodeExSeq = /-u(?:-[0-9a-z]{2,8})+/gi; // See `extension` below

export function /* 9.2.1 */CanonicalizeLocaleList (locales) {
// The abstract operation CanonicalizeLocaleList takes the following steps:
Expand Down
2 changes: 2 additions & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import "./12.datetimeformat.js";

import ls from "./13.locale-sensitive-functions.js";

import "./14.locale.js";

defineProperty(Intl, '__applyLocaleSensitivePrototypes', {
writable: true,
configurable: true,
Expand Down

0 comments on commit ac8f37d

Please sign in to comment.