Skip to content

Arabic Numeric Shaping Support #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,25 @@ module.exports = function( grunt ) {
}
}
},
{
name: "globalize.digit-shaper",
include: [ "digit-shaper", "number/numbering-system-digits-map" ],
exclude: [
"cldr",
"cldr/event",
"cldr/supplemental",
"./core",
"./number/format",
"./plural"
],
create: true,
override: {
wrap: {
startFile: "src/build/intro-digit-shaper.js",
endFile: "src/build/outro.js"
}
}
},
{
name: "globalize-runtime",
include: [ "core-runtime" ],
Expand Down Expand Up @@ -535,6 +554,7 @@ module.exports = function( grunt ) {
"tmp/globalize/plural.min.js": [ "dist/globalize/plural.js" ],
"tmp/globalize/message.min.js": [ "dist/globalize/message.js" ],
"tmp/globalize/relative-time.min.js": [ "dist/globalize/relative-time.js" ],
"tmp/globalize/digit-shaper.min.js": [ "dist/globalize/digit-shaper.js" ],

"tmp/globalize-runtime.min.js": [ "dist/globalize-runtime.js" ],
"tmp/globalize-runtime/currency.min.js": [
Expand Down
55 changes: 55 additions & 0 deletions doc/api/digit-shaper/digit-shaper-shape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## .digitShaper( [options] ) ➜ function( value )

Return a function that shapes digits according to the given options and default/instance locale.

The returned function is invoked with one argument: the string `value` with digits to be shaped.

### Parameters

**options** Optional

A JSON object including none or any of the following options.

> **shaperType** Optional
> String `National` (default), `Contextual` or `None`.
>
> **textDir**
> String `ltr`, `rtl`, or `auto` (default).

**value**

String with digits to be shaped, eg. `"اول 123 abc 123"`.


### Example

Prior to using any digit shaper methods, you must load `cldr/main/{locale}/numbers.json`
and `cldr/supplemental/numberingSystems.jsons`.
Read [CLDR content][] if you need more information.

[CLDR content]: ../../../README.md#2-cldr-content

You can use the static method `Globalize.digitShaper()`, which uses the default locale.

```javascript
var shaper;

Globalize.locale( "ar" );
shaper = Globalize.digitShaper( {"shaperType": "Contextual", "textDir": "rtl"} );

shaper( "اول 123 abc 123" );
// > "اول ١٢٣ abc 123"

shaper = Globalize.digitShaper( {"shaperType": "National", "textDir": "rtl"} );
shaper( "اول 123 abc 123" );
// > "اول ١٢٣ abc ١٢٣"
```

You can use the instance method `.digitShaper()`, which uses the instance locale.

```javascript
var shaper = new Globalize( "ar" ).digitShaper( {"shaperType": "Contextual", "textDir": "rtl"} );

shaper( "اول 123 abc 123" );
// > "اول ١٢٣ abc 123"
```
46 changes: 46 additions & 0 deletions src/build/intro-digit-shaper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Globalize v@VERSION
*
* http://github.com/jquery/globalize
*
* Copyright 2010, 2014 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: @DATE
*/
/*!
* Globalize v@VERSION @DATE Released under the MIT license
* http://git.io/TrdQbw
*/
(function( root, factory ) {

// UMD returnExports
if ( typeof define === "function" && define.amd ) {

// AMD
define([
"cldr",
"../globalize",
"./number",
"cldr/event",
"cldr/supplemental"
], factory );
} else if ( typeof exports === "object" ) {

// Node, CommonJS
module.exports = factory( require( "cldrjs" ), require( "globalize" ) );
} else {

// Extend global
factory( root.Cldr, root.Globalize );
}
}(this, function( Cldr, Globalize ) {

var runtimeBind = Globalize._runtimeBind,
validateCldr = Globalize._validateCldr,
validateDefaultLocale = Globalize._validateDefaultLocale,
validateParameterPresence = Globalize._validateParameterPresence,
validateParameterType = Globalize._validateParameterType,
createErrorUnsupportedFeature = Globalize._createErrorUnsupportedFeature,
validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject;
2 changes: 2 additions & 0 deletions src/build/node-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ require( "./globalize/date" );

// Load after globalize/number and globalize/plural
require( "./globalize/relative-time" );

require( "./globalize/digit-shaper" );
77 changes: 77 additions & 0 deletions src/digit-shaper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
define([
"./core",
"./common/runtime-bind",
"./common/validate/cldr",
"./common/validate/default-locale",
"./common/validate/parameter-presence",
"./common/validate/parameter-type/plain-object",
"./common/validate/parameter-type/string",
"./digit-shaper/properties",
"./digit-shaper/shaper-fn",

"cldr/event",
"cldr/supplemental"
], function( Globalize, runtimeBind, validateCldr,
validateDefaultLocale, validateParameterPresence,
validateParameterTypePlainObject, validateParameterTypeString,
digitShaperProperties, digitShaperShaperFn ) {

/**
* .digitShaper( [options] )
*
* @options [Object]:
* - shaperType: [String] "National" (default), "Contextual" or "None".
* - textDir: [String] "ltr", "rtl", or "auto" (default).
*
* Return a function that shapes digits according to the given options and default/instance
* locale.
*/
Globalize.digitShaper =
Globalize.prototype.digitShaper = function( options ) {
var args, cldr, properties, returnFn;

validateParameterTypePlainObject( options, "options" );

options = options || {};
cldr = this.cldr;

args = [ options ];

validateDefaultLocale( cldr );

cldr.on( "get", validateCldr );

options.shaperType = options.shaperType || "National";
options.textDir = options.textDir || "auto";

properties = digitShaperProperties( cldr, options );

cldr.off( "get", validateCldr );

returnFn = digitShaperShaperFn( properties );

runtimeBind( args, cldr, returnFn, [ properties ] );

return returnFn;
};

/**
* .shapeDigit( value [, options] )
*
* @value [String] text with digits to be shaped.
*
* @options [Object]: See numberShaper().
*
* Return string with shaped digits according to the context.
*/
Globalize.shapeDigit =
Globalize.prototype.shapeDigit = function( value, options ) {
validateParameterPresence( value, "value" );
validateParameterTypeString( value, "value" );

return this.digitShaper( options )( value );
};

return Globalize;

});
45 changes: 45 additions & 0 deletions src/digit-shaper/properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
define([
"../number/numbering-system-digits-map"
], function( numberNumberingSystemDigitsMap ) {

/**
* digitShaperProperties( cldr , options )
*
* @cldr [Cldr instance].
*
* @options [Object]:
* - shaperType: [String] "National" (default), "Contextual" or "None".
* - textDir [String] "ltr", "rtl", or "auto" (default).
*
* Return the processed properties that will be used in digit-shaper/shape.
*/
return function( cldr, options ) {
var properties, nuDigitsMap, nationalDigits;

nationalDigits = numberNumberingSystemDigitsMap( cldr );

if ( cldr.locale === "en" ) {
nationalDigits = "0123456789";
}

nuDigitsMap = nationalDigits.split( "" ).reduce(function( object, localizedDigit, i ) {
object[ i ] = localizedDigit;
object[ localizedDigit ] = String( i );
return object;
}, {} );

properties = options;
properties.locale = cldr.locale;
properties.nuDigitsMap = nuDigitsMap;
properties.nationalDigitsRegex = new RegExp ( "[" + nationalDigits + "]", "g" );

// Return:
// @shaperType [String] "National" (default), "Contextual" or "Latin".
// @textDir [String] "ltr", "rtl", or "auto" (default).
// @locale [String] The target locale to convert digits to.
// @nuDigitsMap [Object] Digits map for both localized & Latin digits.
// @nationalDigitsRegex [RegExp] Regular Expression for National digits.
return properties;
};

});
47 changes: 47 additions & 0 deletions src/digit-shaper/shape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
define( [ "./shape/contextual-ar" ], function( digitShaperShapeContextualAr ) {
/**
* shape( value, properties )
*
* @value [String].
*
* @properties [Object] Output of digit-shaper/properties.
*
* Return string with shaped digits according to the context.
*/
return function( value, properties ) {
var shaperType, textDir, locale, nuDigitsMap, shapeFromLatinToNational,
shapeFromNationalToLatin, nationalDigitsRegex;

shaperType = properties.shaperType;
textDir = properties.textDir;
locale = properties.locale;
nuDigitsMap = properties.nuDigitsMap;
nationalDigitsRegex = properties.nationalDigitsRegex;

shapeFromLatinToNational = function( text ) {
return text.replace( /[0-9]/g, function( c ) {
return nuDigitsMap[ +c ];
});
};

shapeFromNationalToLatin = function( text ) {
return text.replace( nationalDigitsRegex, function( c ) {
return nuDigitsMap[ +c ];
});
};

switch ( shaperType ) {
case "National":
return shapeFromLatinToNational( value );
case "Contextual":
if ( locale.indexOf( "ar" ) === 0 || locale.indexOf( "fa" ) === 0 ) {
return digitShaperShapeContextualAr( value, nuDigitsMap,
textDir === "rtl" ? 2 : 1 );
}
/* falls through */
default: return value;
}

};

});
65 changes: 65 additions & 0 deletions src/digit-shaper/shape/contextual-ar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
define( [], function() {
/**
* shapeContextualAr( text, nuDigitsMap, context )
*
* @text [String] String to be shaped according to the context.
*
* @nuDigitsMap [Object] Digits map for both Arabic & Latin digits.
*
* @context [Integer] The current effective context. "1" Latin context, "2" Arabic context.
*
* Return function that shapes digits according to the context.
*/
return function( text, nuDigitsMap, context ) {

/**
* As of Unicode 7.0, the Latin script is contained in the following blocks:
* Basic Latin, 0000–007F.
* Latin Extended-A, 0100–017F
* Latin Extended-B, 0180–024F
* IPA Extensions, 0250–02AF
* Spacing Modifier Letters, 02B0–02FF
* Phonetic Extensions, 1D00–1D7F
* Phonetic Extensions Supplement, 1D80–1DBF
* Latin Extended Additional, 1E00–1EFF
* Superscripts and Subscripts, 2070-209F
* Letter-like Symbols, 2100–214F
* Number Forms, 2150–218F
* Latin Extended-C, 2C60–2C7F
* LatinExtended-D, A720–A7FF
* Latin Extended-E, AB30–AB6F
* Alphabetic Presentation Forms (Latin ligatures) FB00–FB4F
* Half-width and Full-width Forms (fullwidthLatin letters) FF00–FFEF
*/

/**
* weakLatin: /[\u0000-\u0040\u005B-\u0060\u007B-\u007F\u0080-\u00A9\u00AB-\u00B4
* \u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02B9-\u02BA\u02C2-\u02CF\u02D2-\u02DF
* \u02E5-\u02ED\u02EF-\u02FF\u2070\u2074-\u207E\u2080-\u208E\u2100-\u2101\u2103-\u2106
* \u2108-\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A-\u213B
* \u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189\uA720-\uA721\uA788\uFF01-\uFF20
* \uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE]/
*/

/**
* @regex [Regex]
* To detect (Latin digit) | (Arabic digit) | (strong Arabic letter) | (strong Latin letter).
* Strong Latin letter is a letter which is not Arabic/Latin digit & not weak Arabic/Latin letter
* & not strong Arabic letter.
*/
var regex = /([0-9])|([\u0660-\u0669])|([\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE]+)|([^0-9\u0660-\u0669\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FF\u0750-\u077F\u08A0-\u08E3\u200F\u202B\u202E\u2067\uFB50-\uFD3D\uFD40-\uFDCF\uFDF0-\uFDFC\uFDFE-\uFDFF\uFE70-\uFEFE\u0600-\u0607\u0609-\u060A\u060C\u060E-\u061A\u064B-\u066C\u0670\u06D6-\u06E4\u06E7-\u06ED\u06F0-\u06F9\u08E4-\u08FF\uFD3E-\uFD3F\uFDD0-\uFDEF\uFDFD\uFEFF\u0000-\u0040\u005B-\u0060\u007B-\u007F\u0080-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02B9-\u02BA\u02C2-\u02CF\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u02FF\u2070\u2074-\u207E\u2080-\u208E\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A-\u213B\u2140-\u2144\u214A-\u214D\u2150-\u215F\u2189\uA720-\uA721\uA788\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE]+)/g;

return text.replace( regex, function( match, latinDigit,
arabicDigit, strongArabic, strongLatin ) {
if ( latinDigit ) {
return context === 2 ? nuDigitsMap[ +latinDigit ] : latinDigit;
}else if ( strongArabic ) {
context = 2;
}else if ( strongLatin ) {
context = 1;
}
return match;
});
};

});
Loading