Skip to content

Latest commit

 

History

History
824 lines (793 loc) · 30.7 KB

README.md

File metadata and controls

824 lines (793 loc) · 30.7 KB

jQuery Global plugin v1.0.0pre.

_Note: This plugin is currently in beta form and may change significantly before version 1.0 is released.

Introduction

This jQuery plugin enables complex culture-aware number and date parsing and formatting, including the raw culture information for hundreds of different languages and countries, as well as an extensible system for localization.

Why Globalization?

Each language, and the countries that speak that language, have different expectations when it comes to how numbers (including currency and percentages) and dates should appear. Obviously, each language has different names for the days of the week and the months of the year. But they also have different expectations for the structure of dates, such as what order the day, month and year are in. In number formatting, not only does the character used to deliniate number groupings and the decimal portion differ, but the placement of those characters differ as well.

A user using an application should be able to read and write dates and numbers in the format they are accustomed to. This plugin makes this possible, providing an API to convert user-entered number and date strings - in their own format - into actual numbers and dates, and conversely, to format numbers and dates into that string format.

What is a Culture?

jquery.global.js defines roughly 350 cultures. Part of the reason for this large number, besides there being a lot of cultures in the world, is because for some languages, expectations differ amoung the countries that speak it. English, for example, is an official language in dozens of countries. Despite the language being English, the expected date formatting still greatly differs between them.

So, it does not seem useful to define cultures by their language alone. Nor is it useful to define a culture by its country alone, as many countries have several official languages, spoken by sizable populations. Therefore, cultures are defined as a combination of the language and the country speaking it. Each culture is given a unique code that is a combination of an ISO 639 two-letter lowercase culture code for the language, and a two-letter uppercase code for the country or region. For example, "en-US" is the culture code for English in the United States.

Yet, it is perhaps unreasonable to expect application developers to cater to every possible language/country combination perfectly. It is important then to define so-called 'neutral' cultures based on each language. These cultures define the most likely accepted set of rules by anyone speaking that language, whatever the country. Neutral cultures are defined only by their language code. For example, "es" is the neutral culture for Spanish.

jQuery.global.cultures

A mapping of culture codes to culture objects. For example, jQuery.global.cultures.fr is an object representing the complete culture definition for the neutral French culture. Note that jquery.global.js alone only includes an English culture. To get additional cultures, you must seperately reference one or more of the culture scripts that come with it. You can see in the section Defining Culture Information below which fields are defined in each culture.

jQuery.global.culture

This property is set to the culture currently selected. An optional culture can be specified for the various parsing and formatting functions; it defaults to jQuery.global.culture when omitted. To change the current culture, set it to one of the available cultures, for example:

jQuery.global.culture = jQuery.global.cultures["fr-FR"];

jQuery.global.preferCulture(name)

An application that supports globalization and/or localization will need to have a way to determine the user's preference. Attempting to automatically determine the appropriate culture is useful, but it is good practice to always offer the user a choice, by whatever means.

Whatever your mechanism, it is likely that you will have to correlate the user's preferences with the list of cultures supported in the app. This function allows you to select the best match given the culture scripts that you have included and to set the jQuery.global.culture property to the culture which the user prefers.

If you pass an array of names instead of a single name string, the first culture for which there is a match (that culture's script has been referenced) will be used. If none match, the search restarts using the corresponding neutral cultures. For example, if the application has included only the neutral "fr" culture, any of these would select it:

jQuery.global.preferCulture("fr");
alert(jQuery.global.culture.name) // "fr"

jQuery.global.preferCulture("fr-FR"); alert(jQuery.global.culture.name) // "fr-FR"

jQuery.global.preferCulture(["es-MX", "fr-FR"]); alert(jQuery.global.culture.name) // "es-MX"

In any case, if no match is found the neutral English culture "en" is selected by default.

Each culture string may also follow the pattern defined in RFC2616 sec 14.4. That is, a culture name may include a 'quality' value that indicates an estimate of the user's preference for the language.

jQuery.global.preferCulture("fr;q=0.4, es;q=0.5, he");

In this example, the neutral Hebrew culture "he" is given top priority (an unspecified quality is equal to 1). If that language is not an exact match for any of the cultures available in jQuery.global.cultures, then "es" is the next highest priority with 0.5, etc. If none of these match, just like with the array syntax, the search starts over and the same rules are applied to the corresponding neutral language culture for each. If still none match, the neutral English culture "en" is used.

jQuery.global.findClosestCulture(name)

Just like preferCulture(name), but it just returns the matching culture, if any, without setting it to the jQuery.global.culture property.

jQuery.global.format(value, format, culture)

Formats a date or number according to the given format string and the given culture (or the current culture if not specified). See the sections Number Formatting and Date Formatting below for details on the available formats.

// assuming a culture with number grouping of 3 digits,
// using "," separator and "." decimal symbol.
jQuery.global.format(1234.567, "n"); // "1,234.57"
jQuery.global.format(1234.567, "n1"); // "1,234.6"
jQuery.global.format(1234.567, "n0"); // "1,235"

// assuming a culture with "/" as the date separator symbol jQuery.global.format(new Date(1955,10,5), "yyyy/MM/dd"); // "1955/11/05" jQuery.global.format(new Date(1955,10,5), "dddd MMMM d, yyyy"); // "Saturday November 5, 1955"

jQuery.global.parseInt(value, radix, culture)

Parses a string representing a whole number in the given radix (10 by default), taking into account any formatting rules followed by the given culture (or the current culture, if not specified).

// assuming a culture where "," is the group separator
// and "." is the decimal separator
jQuery.global.parseInt("1,234.56"); // 1234
// assuming a culture where "." is the group separator
// and "," is the decimal separator
jQuery.global.parseInt("1.234,56"); // 1234

jQuery.global.parseFloat(value, radix, culture)

Parses a string representing a floating point number in the given radix (10 by default), taking into account any formatting rules followed by the given culture (or the current culture, if not specified).

// assuming a culture where "," is the group separator
// and "." is the decimal separator
jQuery.global.parseFloat("1,234.56"); // 1234.56
// assuming a culture where "." is the group separator
// and "," is the decimal separator
jQuery.global.parseFloat("1.234,56"); // 1234.56

jQuery.global.parseDate(value, formats, culture)

Parses a string representing a date into a JavaScript Date object, taking into account the given possible formats (or the given culture's set of default formats if not given). As before, the current culture is used if one is not specified.

jQuery.global.culture = jQuery.culture.en;
jQuery.global.parseDate("1/2/2003"); // Thu Jan 02 2003
jQuery.global.culture = jQuery.culture.fr;
jQuery.global.parseDate("1/2/2003"); // Sat Feb 01 2003

jQuery.global.localize(key, culture, value)

Gets or sets a localized value. This function allows you to extend the information available to a particular culture, and to easily retrieve it without worrying about finding the most appropriate culture. For example, to define the word 'translate' in French:

jQuery.global.localize("translate", "fr", "traduire");
The value may be any value you wish: a string, number, object, etc. You can then define a grouping of localized values common to a feature, plugin, or application.
jQuery.global.localize("myplugin", "fr", {
    foo: "foo",
    bar: "bar"
});

var obj = jQuery.global.localize("myplugin", "fr"); alert(obj.foo); // "foo"

Note that localize() will find the closest match available per the same semantics as the jQuery.global.findClosestCulture() function. If there is no match, the translation given is for the neutral English culture "en" by default.

// falsy values "", null, undefined...
// are all equivalent to "en" or "default"
jQuery.global.localize("myplugin", "", {
    foo: "foo (en)",
    bar: "bar (en)"
});
jQuery.global.localize("myplugin", "fr", {
    foo: "foo (fr)",
    bar: "bar (fr)"
});

jQuery.global.culture = jQuery.global.cultures["fr"];
alert(jQuery.global.localize("myplugin").foo); // "foo (fr)"

jQuery.global.culture = jQuery.global.cultures["fr-FR"];
alert(jQuery.global.localize("myplugin").foo); // "foo (fr)"

jQuery.global.culture = jQuery.global.cultures["es-MX"];
alert(jQuery.global.localize("myplugin").foo); // "foo (en)"

Also note that localize() does not require loading the culture information script. You may use localize() for localization purposes without utilizing the parsing and formatting functions which depend on the cultures. If you do use both, it does not matter what order you include them in, either may be first -- the jquery.global.<code>.js script, or your own script which uses localize(), as long as property names do not overlap.

Utilizing and Extending Cultures

The culture information included with each culture is mostly necessary for the parsing and formatting methods, but not all of it. For example, the Native and English names for each culture is given, as well as a boolean indicating whether the language is right-to-left. This may be useful information for your own purposes. You may also add to the culture information directly if so desired. It is important to do so in a way that handles the fact that the culture info may not be provided, may not be provided yet, or may already be provided. Using jQuery's extend() method, it is possible to define a culture in a way that both defines it if it does not exist and adds to it if it does exist.

As an example, in the U.S., the word 'billion' means the number 1,000,000,000 (9 zeros). But in other countries, that number is '1000 million' or a 'milliard', and a billion is 1,000,000,000,000 (12 zeros). If you needed to provide functionality to your app or custom plugin that needed to know how many zeros are in a 'billion', you could extend the culture information as follows:

// define culture information without overwriting any existing values
jQuery.global.cultures.fr = jQuery.extend(true, {
    numberFormat: {
        billionZeros: 12
    }
}, jQuery.global.cultures.fr);
Using this mechanism, the "fr" culture will be created if it does not exist. And if it does, the given values will be added to it, taking care not to overwrite anything that is already defined (if you'd prefer to overwrite, you'd switch the last two arguments). When the jquery.global.fr.js script is eventually included, it too uses this technique, ensuring addition to the already-defined culture information.

Defining Culture Information

Each culture is defined in its own script with the naming scheme jquery.global.<code>.js (along with its minified version, jquery.global.<code>.min.js). You may include any number of these scripts, making them available in the jQuery.global.cultures mapping. Including one of these scripts does NOT automatically make it the current culture selected in the jQuery.global.culture property.

The neutral English culture is defined directly in jquery.global.js, and set both to the properties "en" and "default" of the jQuery.global.cultures mapping. Extensive comments describe the purpose of each of the fields defined.

Looking at the souce code of the scripts for each culture, you will notice that each script uses jQuery's $.extend to copy the properties of the neutral English culture "en", as a common basis, and defines only the properties that differ from neutral English.

The neutral English culture is listed here along with the comments:

jQuery.global.cultures["default"] = jQuery.global.cultures.en = {
    // A unique name for the culture in the form
    // <language code>-<country/region code>
    name: "English",
    // the name of the culture in the English language
    englishName: "English",
    // the name of the culture in its own language
    nativeName: "English",
    // whether the culture uses right-to-left text
    isRTL: false,
    // "language" is used for so-called 'specific' cultures.
    // For example, the culture "es-CL" means Spanish in Chili.
    // It represents the Spanish-speaking culture as it is in Chili,
    // which might have different formatting rules or even translations
    // than Spanish in Spain. A 'neutral' culture is one that is not
    // specific to a region. For example, the culture "es" is the generic
    // Spanish culture, which may be a more generalized version of the language
    // that may or may not be what a specific culture expects.
    // For a specific culture like "es-CL", the "language" field refers to the
    // neutral, generic culture information for the language it is using.
    // This is not always a simple matter of the string before the dash.
    // For example, the "zh-Hans" culture is neutral (Simplified Chinese).
    // And the "zh-SG" culture is Simplified Chinese in Singapore, whose
    // language field is "zh-CHS", not "zh".
    // This field should be used to navigate from a specific culture to it's
    // more general, neutral culture. If a culture is already as general as it
    // can get, the language may refer to itself.
    language: "en",
    // "numberFormat" defines general number formatting rules, like the digits
    // in each grouping, the group separator, and how negative numbers are
    // displayed.
    numberFormat: {
        // [negativePattern]
        // Note, numberFormat.pattern has no 'positivePattern' unlike percent
        // and currency, but is still defined as an array for consistency with
        // them.
        //    negativePattern: one of "(n)|-n|- n|n-|n -"
        pattern: ["-n"],
        // number of decimal places normally shown
        decimals: 2,
        // string that separates number groups, as in 1,000,000
        ',': ",",
        // string that separates a number from the fractional portion,
        // as in 1.99
        '.': ".",
        // array of numbers indicating the size of each number group.
        groupSizes: [3],
        // symbol used for positive numbers
        '+': "+",
        // symbol used for negative numbers
        '-': "-",
        percent: {
            // [negativePattern, positivePattern]
            //     negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %"
            //     positivePattern: one of "n %|n%|%n|% n"
            pattern: ["-n %","n %"],
            // number of decimal places normally shown
            decimals: 2,
            // array of numbers indicating the size of each number group.
            groupSizes: [3],
            // string that separates number groups, as in 1,000,000
            ',': ",",
            // string that separates a number from the fractional portion, as in 1.99
            '.': ".",
            // symbol used to represent a percentage
            symbol: "%"
        },
        currency: {
            // [negativePattern, positivePattern]
            //     negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)"
            //     positivePattern: one of "$n|n$|$ n|n $"
            pattern: ["($n)","$n"],
            // number of decimal places normally shown
            decimals: 2,
            // array of numbers indicating the size of each number group.
            groupSizes: [3],
            // string that separates number groups, as in 1,000,000
            ',': ",",
            // string that separates a number from the fractional portion, as in 1.99
            '.': ".",
            // symbol used to represent currency
            symbol: "$"
        }
    },
    // "calendars" property defines all the possible calendars used by this
    // culture. There should be at least one defined with name "standard" which
    // is the default calendar used by the culture.
    // A calendar contains information about how dates are formatted,
    // information about the calendar's eras, a standard set of the date
    // formats, translations for day and month names, and if the calendar is
    // not based on the Gregorian calendar, conversion functions to and from
    // the Gregorian calendar.
    calendars: {
        standard: {
            // name that identifies the type of calendar this is
            name: "Gregorian_USEnglish",
            // separator of parts of a date (e.g. '/' in 11/05/1955)
            '/': "/",
            // separator of parts of a time (e.g. ':' in 05:44 PM)
            ':': ":",
            // the first day of the week (0 = Sunday, 1 = Monday, etc)
            firstDay: 0,
            days: {
                // full day names
                names: ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
                // abbreviated day names
                namesAbbr: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
                // shortest day names
                namesShort: ["Su","Mo","Tu","We","Th","Fr","Sa"]
            },
            months: [
                // full month names (13 months for lunar calendars -- 13th month should be "" if not lunar)
                names: ["January","February","March","April","May","June","July","August","September","October","November","December",""],
                // abbreviated month names
                namesAbbr: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",""]
            ],
            // AM and PM designators in one of these forms:
            // The usual view, and the upper and lower case versions
            //      [standard,lowercase,uppercase]
            // The culture does not use AM or PM (likely all standard date
            // formats use 24 hour time)
            //      null
            AM: ["AM", "am", "AM"],
            PM: ["PM", "pm", "PM"],
            eras: [
                // eras in reverse chronological order.
                // name: the name of the era in this culture (e.g. A.D., C.E.)
                // start: when the era starts in ticks, null if it is the
                //        earliest supported era.
                // offset: offset in years from gregorian calendar
                {"name":"A.D.","start":null,"offset":0}
            ],
            // when a two digit year is given, it will never be parsed as a
            // four digit year greater than this year (in the appropriate era
            // for the culture)
            // Set it as a full year (e.g. 2029) or use an offset format
            // starting from the current year: "+19" would correspond to 2029
            // if the current year is 2010.
            twoDigitYearMax: 2029,
            // set of predefined date and time patterns used by the culture.
            // These represent the format someone in this culture would expect
            // to see given the portions of the date that are shown.
            patterns: {
                // short date pattern
                d: "M/d/yyyy",
                // long date pattern
                D: "dddd, MMMM dd, yyyy",
                // short time pattern
                t: "h:mm tt",
                // long time pattern
                T: "h:mm:ss tt",
                // long date, short time pattern
                f: "dddd, MMMM dd, yyyy h:mm tt",
                // long date, long time pattern
                F: "dddd, MMMM dd, yyyy h:mm:ss tt",
                // month/day pattern
                M: "MMMM dd",
                // month/year pattern
                Y: "yyyy MMMM",
                // S is a sortable format that does not vary by culture
                S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss"
            }
            // optional fields for each calendar:
            /*
            monthsGenitive:
                Same as months but used when the day preceeds the month.
                Omit if the culture has no genitive distinction in month names.
                For an explanation of genitive months, see
                http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx
            convert:
                Allows for the support of non-gregorian based calendars. This
                "convert" object defines two functions to convert a date to and
                from a gregorian calendar date:
                    fromGregorian(date)
                        Given the date as a parameter, return an array with
                        parts [year, month, day] corresponding to the
                        non-gregorian based year, month, and day for the
                        calendar.
                    toGregorian(year, month, day)
                        Given the non-gregorian year, month, and day, return a
                        new Date() object set to the corresponding date in the
                        gregorian calendar.
            */
        }
    }
}

Each culture can have several possible calendars. The calendar named "standard" is the default calendar used by that culture. You may change the calendar in use by setting the "calendar" field. Take a look at the calendars defined by each culture by looking at the script or enumerating its calendars collection.

// switch to a non-standard calendar
$.global.culture.calendar = $.global.culture.calendars.SomeOtherCalendar;
// back to the standard calendar
$.global.culture.calendar = $.global.culture.calendars.standard;

Number Formatting

When formatting a number with format(), the main purpose is to convert the number into a human readable string using the culture's standard grouping and decimal rules. The rules between cultures can vary a lot. For example, in some cultures, the grouping of numbers is done unevenly. In the "te-IN" culture (Telugu in India), groups have 3 digits and then 2 digits. The number 1000000 (one million) is written as "10,00,000". Some cultures do not group numbers at all.

There are four main types of number formatting:

  • n for number
  • d for decimal digits
  • p for percentage
  • c for currency
Even within the same culture, the formatting rules can vary between these four types of numbers. For example, the expected number of decimal places may differ from the number format to the currency format. Each format token may also be followed by a number. The number determines how many decimal places to display for all the format types except decimal, for which it means the minimum number of digits to display, zero padding it if necessary. Also note that the way negative numbers are represented in each culture can vary, such as what the negative sign is, and whether the negative sign appears before or after the number. This is especially apparent with currency formatting, where many cultures use parentheses instead of a negative sign.
// just for example - will vary by culture
jQuery.global.format(123.45, "n"); // 123.45
jQuery.global.format(123.45, "n0"); // 123
jQuery.global.format(123.45, "n1"); // 123.5

jQuery.global.format(123.45, "d"); // 123 jQuery.global.format(12, "d3"); // 012

jQuery.global.format(123.45, "c"); // $123.45 jQuery.global.format(123.45, "c0"); // $123 jQuery.global.format(123.45, "c1"); // $123.5 jQuery.global.format(-123.45, "c"); // ($123.55)

jQuery.global.format(0.12345, "p"); // 12.35 % jQuery.global.format(0.12345, "p0"); // 12 % jQuery.global.format(0.12345, "p4"); // 12.3450 %

Parsing with parseInt and parseFloat also accepts any of these formats.

Date Formatting

Date formatting varies wildly by culture, not just in the spelling of month and day names, and the date separator, but by the expected order of the various date components, whether to use a 12 or 24 hour clock, and how months and days are abbreviated. Many cultures even include "genitive" month names, which are different from the typical names and are used only in certain cases.

Also, each culture has a set of 'standard' or 'typical' formats. For example, in "en-US", when displaying a date in its fullest form, it looks like "Saturday, November 05, 1955". Note the non-abbreviated day and month name, the zero padded date, and four digit year. So, jquery.global.js expects a certain set of 'standard' formatting strings for dates in the "patterns" property of the "standard" calendar of each culture, that describe specific formats for the culture. The third column shows example values in the neutral English culture "en-US"; see the second table for the meaning tokens used in date formats.

Format Meaning "en-US"
f Long Date, Short Time dddd, MMMM dd, yyyy h:mm tt
F Long Date, Long Time dddd, MMMM dd, yyyy h:mm:ss tt
t Short Time h:mm tt
T Long Time h:mm:ss tt
d Short Date M/d/yyyy
D Long Date dddd, MMMM dd, yyyy
Y Month/Year MMMM, yyyy
M Month/Day yyyy MMMM

In addition to these standard formats, there is the 'S' format. This is a sortable format that is identical in every culture: "yyyy'-'MM'-'dd'T'HH':'mm':'ss".

When more specific control is needed over the formatting, you may use any format you wish by specifing the following custom tokens:

Token Meaning Example
d Day of month (no leading zero) 5
dd Day of month (leading zero) 05
ddd Day name (abbreviated) Sat
dddd Day name (full) Saturday
M Month of year (no leading zero) 9
MM Month of year (leading zero) 09
MMM Month name (abbreviated) Sept
MMMM Month name (full) September
yy Year (two digits) 55
yyyy Year (four digits) 1955
'literal' Literal Text 'of the clock'
\' Single Quote 'o'\''clock'
m Minutes (no leading zero) 9
mm Minutes (leading zero) 09
h Hours (12 hour time, no leading zero) 6
hh Hours (12 hour time, leading zero) 06
H Hours (24 hour time, no leading zero) 5 (5am) 15 (3pm)
HH Hours (24 hour time, leading zero) 05 (5am) 15 (3pm)
s Seconds (no leading zero) 9
ss Seconds (leading zero) 09
f Deciseconds 1
ff Centiseconds 11
fff Milliseconds 111
t AM/PM indicator (first letter) A or P
tt AM/PM indicator (full) AM or PM
z Timezone offset (hours only, no leading zero) -8
zz Timezone offset (hours only, leading zero) -08
zzz Timezone offset (full hours/minutes) -08:00
g or gg Era name A.D.