Specialty plates in circuit courts, and the parties’ arguments at the Supreme Court

Yesterday I discussed the background to Walker v. Texas Division, Sons of Confederate Veterans. Stated briefly, Texas denied Texas SCV‘s application for a specialty license plate with a Confederate flag on it, because the design might be “offensive”. The question is whether Texas is required by the First Amendment to grant the application.

Today I discuss how specialty plate programs have fared in lower courts, and the arguments Texas and Texas SCV bring to the case.

In the courts

Almost every circuit court has required that specialty plate programs be viewpoint-neutral, not restricting designs because of their views. (And the one exception judged a program without an open invitation for designs.) So it’s unsurprising that Texas SCV won its Fifth Circuit case.

Texas appealed to the Supreme Court, which agreed to answer two questions. Are specialty plate programs “government speech” that need not be viewpoint-neutral, such that the design can be rejected as “offensive” (or, indeed, for almost any reason)? And did Texas discriminate by viewpoint in rejecting Texas SCV’s design?

Texas’s argument

Texas says license plates are entirely the government speaking, and it can say or not say whatever it wants. Texas relies on two cases: Pleasant Grove City v. Summum, in which a city’s approval of a limited set of monuments in its city park (and denial of a particular monument) was deemed government speech; and Johanns v. Livestock Marketing Association, in which a government beef-promotion plan that exacted a fee from beef producers to support speech (including the Beef. It’s What’s For Dinner tagline) was deemed government speech that program participants couldn’t challenge on the grounds that it compelled them to speak.

According to Texas, its specialty plates are government speech because Texas “effectively control[s]” the whole program. What matters is whether Texas “exercises final approval authority over every word used” — and it does. Texas allows private citizens to participate, but it has “final approval authority” over every design. Texas also argues that it can’t be compelled to speak by displaying the Confederate flag. By making a license plate, the state’s authority backs (or doesn’t back) every design approved or rejected. Plate purchasers shouldn’t be able to force Texas to espouse the views of an unwanted specialty plate, which drivers would then ascribe to Texas.

The Texas-approved Mighty Fine Burger specialty plate

And of course, Texas says ruling against them would lead to “untenable consequences”. For every “Stop Child Abuse” plate there’d have to be an opposing plate supporting child abuse, and so on for the whole parade of horribles. Texas particularly notes that the Eighth Circuit forced Missouri to let the Ku Klux Klan join the state’s Adopt-a-Highway program under this logic. (The person behind me in the oral argument line related that one of the highways entering Arkansas was adopted by the KKK under that rule, giving Arkansas visitors that delightful first impression of the state.)

Texas also asserted that assessing how members of the public view a specialty plate is “an objective inquiry”, so that deciding a specialty plate “might be offensive” doesn’t discriminate on the basis of the specialty plate’s viewpoint. As to the Fifth Circuit’s criticism of the “unbridled discretion” provided by the “might be offensive” bar, Texas instead describes it as “discriminating among levels of offensiveness”, such latitude permitted because the state is “assisting speech”.

Texas SCV’s argument

Texas SCV says Texas is being hypocritical. The Capitol gift shop sells Confederate flags. Texas recognizes a state Confederate Heroes Day. It maintains monuments to Confederate soldiers. Either Texas doesn’t really think the Confederate flag is offensive to the public, or its other “government speech” is flatly inconsistent with its specialty-plate stance.

Texas SCV also distinguishes the plates designed by the state legislature from plates designed by private entities. The former are the product of the government, but the only government involvement in the latter is in approval or disapproval. The driver has ultimate control, because only when he designs a plate and ultimately drives a vehicle with it does speech occur. And under Wooley v. Maynard — a case where a Jehovah’s Witness protested New Hampshire’s fining of people who covered up “Live Free or Die” on their license plates, and the Court said New Hampshire couldn’t force a person to espouse the state motto — it’s the individual’s speech (at least for non-legislatively-designed plates).

Texas SCV brushes off Summum and Johanns. Permanent monuments in parks have always been associated with the government, because parks physically can’t accommodate all monuments. Not so for license plates. (And Texas’s $8000 deposit covers startup costs that might justify treating rare plates differently.) And while the beef-promotion messages were part of a “coordinated program” by government to “advance the image and desirability of beef and beef products”, privately-designed specialty plates are not — especially as their fullycontradictory messages are “consistent” only as a fundraiser.

Finally, given that privately-designed specialty plates are private speech, the First Amendment requires that restrictions be viewpoint-neutral. By restricting Texas SCV’s message based on its potential for offensiveness, Texas endorsed viewpoints that deem the Confederate flag racist and discriminated against viewpoints that do not.

Tomorrow, analysis of Texas’s government speech and compelled speech arguments.


Texas specialty license plates

Yesterday I discussed the second Supreme Court oral argument I attended in a recent trip to the Supreme Court. Today I describe the basic controversy in the first oral argument I attended, in a case potentially implicating the First Amendment. First Amendment law is complicated, so this is the first of several posts on the case.

Texas specialty license plates

State license plates, affixed to vehicles to permit legal use on public roads, typically come in one or very few standard designs. But in many states you can purchase a specialty plate with special imagery, designs, coloring, &c. (Specialty plates are distinct from “vanity” plates. A vanity plate has custom letters and numbers, e.g. a vegetarian might request LUVTOFU.) Some state legislatures direct that specialty designs delivering particular messages be offered. And some state legislatures enact laws that permit organizations or individuals to design specialty plates.

The state of Texas sells both legislatively-requested designs and designs ordered by organizations or individuals. (The latter kind require an $8000 bond, covering ramp-up costs until a thousand plates are sold.) The DMVB evaluates designs for compliance with legislated criteria: for example, reflectivity and legibility concerns. One criterion allows (but does not require) Texas to reject “offensive” plates.

The department may refuse to create a new specialty license plate if the design might be offensive to any member of the public.

An “offensive” specialty plate design

Texas rejected one particular design for just this reason. As they say, a picture is worth a thousand words:

A Texas license plate with the Texas Sons of Confederate Veterans logo on the left side, prominently including a Confederate flag
The Texas Sons of Confederate Veterans’s proposed specialty plate…incorporating a Confederate flag. (Yes, Texas — including Rick Perry and Greg Abbott both — rejected this design.) (source)

For those unfamiliar with American imagery: the central feature of the Texas SCV insignia is the Confederate flag. Evoking many things, but in some minds chiefly representative of revanchist desire to resurrect Southern racism, Jim Crow, and the rest of that sordid time. Such minds naturally find the Confederate flag offensive.

Is the SCV actually racist? (Assuming you don’t construe mere use of the flag as prima facie evidence.) A spokesman denies the claim. Web searches find some who disagree and others who believe it is (or was) of divided view. I find no explicit denunciation of racism on the SCV’s website, but I searched only very briefly. Form your own conclusions.

Tomorrow, specialty plate programs in the courts, and the parties’ arguments.


Police, force, and armed and violent disabled people: San Francisco v. Sheehan

Yesterday I began a series of posts discussing the Supreme Court cases I saw in my latest visit for oral arguments. Today I discuss San Francisco v. Sheehan

San Francisco v. Sheehan concerned a messy use of force by police in San Francisco in responding to a violent, mentally-ill person making threats with a knife — an unhappy situation for all. Very imprecisely, the question is whether the officers used excessive force to subdue an armed and violent, disabled suspect, knowing that suspect might require special treatment under the Americans with Disabilities Act or the Fourth Amendment while being arrested. (Of course, whatever baseline those laws require, police often should and will be held to a higher standard.)

Chief Justice Rehnquist, standing upon a white, blue-outlined Nevada, holding volume 530 of the US Reports
Chief Justice William Rehnquist, one of the other objects of the trip

The obvious prediction

Mildly-interested readers need know but two things to predict this case’s outcome. First, this case arose in the Ninth Circus Circuit: a court regularly with very outlier views. And not solely along the tired left-right axis: when the Court often summarily reverses the Ninth Circuit without even hearing argument, partisanship can play no role. Second, Sheehan must overcome qualified immunity, which for better and worse protects “all but the plainly incompetent” police against lawsuit. These facts typically guarantee San Francisco will win and Sheehan will lose.

That aside, one observation struck me. Stereotyping heavily, it’s surprising that San Francisco in particular would argue, to use overly-reductive descriptions, “for” police and “against” the disabled. Usually we’d assume San Francisco would stand by, not against, underprivileged minorities.

“Bait and switch”

That expectation makes this letter from advocacy groups requesting San Francisco abandon its appeal very interesting. At oral argument Justice Scalia interrupted San Francisco’s argument before it even started to bluntly charge the city with changing its argument, between its request for the Supreme Court to hear the case and when San Francisco presented its argument for why it should win — even calling it a “bait and switch”. Minutes later, Justice Sotomayor echoed his views (in more restrained terms).

When requesting Supreme Court review, San Francisco argued that the ADA “does not require accommodations for armed and violent suspects who are disabled” — during an arrest, all such suspects may be treated identically regardless of ability. In response the Court agreed to decide “whether Title II of the Americans with Disabilities Act requires law enforcement officers to provide accommodations to an armed, violent, and mentally ill suspect” while bringing him into custody.

But San Francisco’s written argument instead argued, “Sheehan was not entitled to receive accommodations in her arrest under Title II of the [ADA]” because her armed violence “posed a direct threat in the reasonable judgment of the officers”. In other words, San Francisco had changed from arguing no armed and violent, disabled suspect deserved an ADA accommodation, to arguing Sheehan particularly deserved no ADA accommodation because she appeared to be a direct threat.

The San Francisco City Hall dome and building

The followup

Thus San Francisco’s argument derailed, on this and other points. Several minutes in Justice Kagan even prefaced a question with, “And while we are talking about questions that are not strictly speaking in the case,” to audience laughter. A Ninth-Circuit, plaintiff-friendly, appeal-by-the-government case is usually a strong bet for reversal, but San Francisco seems to have complicated its own case.

The Court could well dismiss this case as “improvidently granted”, preserving the lower court’s decision without creating precedent. Oral argument raised the possibility, but a month later it seems unlikely. San Francisco’s still likely to win, but the justices’ frustration with San Francisco’s alleged argument change might not bode well when San Francisco next wants the Court to hear a case.

Back to the letter

Again consider the letter urging San Francisco to abandon its appeal. Suppose the letter’s authors first privately requested San Francisco drop the case, resorting to open letter once those overtures failed.

But what if the letter wasn’t a complete failure? Could San Francisco have changed its argument to “split the baby”, protecting its officers and attempting to placate interest groups? The shift couldn’t have responded to just the letter, sent one day before San Francisco made its final argument. But it might have been triggered by prior behind-the-scenes negotiation.

This fanciful possibility requires that the open letter not be San Francisco’s first chance to hear its arguments. It further grants the letter’s authors extraordinary political power…yet too little to change San Francisco’s position. Occam’s Razor absolutely rejects this explanation. But if some involved interest group promptly tried to dissuade San Francisco, the letter might have been partially effective.

Final analysis

Are Justice Scalia’s and Sotomayor’s criticisms reasonable? I didn’t fully read the briefs, and I don’t know when it’s acceptable for a party to change its argument (except by settling the case). It appears to me that San Francisco changed its argument; my sense is doing so but claiming you didn’t is the wrong way to change one’s position. But I don’t know enough to be sure of either conclusion.

As I said yesterday, I didn’t fully prepare for this argument, so I hesitate to say too much. And frankly the messy facts make me glad I don’t have to choose a position. So I’ll leave my discussion at that.

Tomorrow I continue to the primary case I came to see, a First Amendment case.


Another D.C. trip

A month ago, I visited Washington, D.C. to see (unfortunately only a subset of) friends in the area, to get another Supreme Court bobblehead (Chief Justice Rehnquist) — and, naturally, to watch interesting Supreme Court oral arguments. I attended two arguments on March 23: the first for a First Amendment case, the second for (roughly) a police use-of-force case.

Early morning at the Supreme Court, on a cloudy morning with a mostly-blue sky at sunrise
A deep-blue morning at the Court

I did relatively little preparation for the police use-of-force case, limiting myself to the facts, questions presented, and cursory summaries of the parties’ arguments. My discussion of that case will be brief.

But the other case (for which I amply prepared) will receive different treatment. First Amendment law is extraordinarily complicated. A proper treatment of the case, its background, legal analysis, and oral argument discussion well exceeds a single post.

So a post series it is. Tomorrow: the police use-of-force case.


Introducing the JavaScript Internationalization API

(also cross-posted on the Hacks blog — comment over there if you have anything to say)

Firefox 29 issued half a year ago, so this post is long overdue. Nevertheless I wanted to pause for a second to discuss the Internationalization API first shipped on desktop in that release (and passing all tests!). Norbert Lindenberg wrote most of the implementation, and I reviewed it and now maintain it. (Work by Makoto Kato should bring this to Android soon; b2g may take longer due to some b2g-specific hurdles. Stay tuned.)

What’s internationalization?

Internationalization (i18n for short — i, eighteen characters, n) is the process of writing applications in a way that allows them to be easily adapted for audiences from varied places, using varied languages. It’s easy to get this wrong by inadvertently assuming one’s users come from one place and speak one language, especially if you don’t even know you’ve made an assumption.

function formatDate(d)
  // Everyone uses month/date/year...right?
  var month = d.getMonth() + 1;
  var date = d.getDate();
  var year = d.getFullYear();
  return month + "/" + date + "/" + year;

function formatMoney(amount)
  // All money is dollars with two fractional digits...right?
  return "$" + amount.toFixed(2);

function sortNames(names)
  function sortAlphabetically(a, b)
    var left = a.toLowerCase(), right = b.toLowerCase();
    if (left > right)
      return 1;
    if (left === right)
      return 0;
    return -1;

  // Names always sort alphabetically...right?

JavaScript’s historical i18n support is poor

i18n-aware formatting in traditional JS uses the various toLocaleString() methods. The resulting strings contained whatever details the implementation chose to provide: no way to pick and choose (did you need a weekday in that formatted date? is the year irrelevant?). Even if the proper details were included, the format might be wrong e.g. decimal when percentage was desired. And you couldn’t choose a locale.

As for sorting, JS provided almost no useful locale-sensitive text-comparison (collation) functions. localeCompare() existed but with a very awkward interface unsuited for use with sort. And it too didn’t permit choosing a locale or specific sort order.

These limitations are bad enough that — this surprised me greatly when I learned it! — serious web applications that need i18n capabilities (most commonly, financial sites displaying currencies) will box up the data, send it to a server, have the server perform the operation, and send it back to the client. Server roundtrips just to format amounts of money. Yeesh.

A new JS Internationalization API

The new ECMAScript Internationalization API greatly improves JavaScript’s i18n capabilities. It provides all the flourishes one could want for formatting dates and numbers and sorting text. The locale is selectable, with fallback if the requested locale is unsupported. Formatting requests can specify the particular components to include. Custom formats for percentages, significant digits, and currencies are supported. Numerous collation options are exposed for use in sorting text. And if you care about performance, the up-front work to select a locale and process options can now be done once, instead of once every time a locale-dependent operation is performed.

That said, the API is not a panacea. The API is “best effort” only. Precise outputs are almost always deliberately unspecified. An implementation could legally support only the oj locale, or it could ignore (almost all) provided formatting options. Most implementations will have high-quality support for many locales, but it’s not guaranteed (particularly on resource-constrained systems such as mobile).

Under the hood, Firefox’s implementation depends upon the International Components for Unicode library (ICU), which in turn depends upon the Unicode Common Locale Data Repository (CLDR) locale data set. Our implementation is self-hosted: most of the implementation atop ICU is written in JavaScript itself. We hit a few bumps along the way (we haven’t self-hosted anything this large before), but nothing major.

The Intl interface

The i18n API lives on the global Intl object. Intl contains three constructors: Intl.Collator, Intl.DateTimeFormat, and Intl.NumberFormat. Each constructor creates an object exposing the relevant operation, efficiently caching locale and options for the operation. Creating such an object follows this pattern:

var ctor = "Collator"; // or the others
var instance = new Intl[ctor](locales, options);

locales is a string specifying a single language tag or an arraylike object containing multiple language tags. Language tags are strings like en (English generally), de-AT (German as used in Austria), or zh-Hant-TW (Chinese as used in Taiwan, using the traditional Chinese script). Language tags can also include a “Unicode extension”, of the form -u-key1-value1-key2-value2..., where each key is an “extension key”. The various constructors interpret these specially.

options is an object whose properties (or their absence, by evaluating to undefined) determine how the formatter or collator behaves. Its exact interpretation is determined by the individual constructor.

Given locale information and options, the implementation will try to produce the closest behavior it can to the “ideal” behavior. Firefox supports 400+ locales for collation and 600+ locales for date/time and number formatting, so it’s very likely (but not guaranteed) the locales you might care about are supported.

Intl generally provides no guarantee of particular behavior. If the requested locale is unsupported, Intl allows best-effort behavior. Even if the locale is supported, behavior is not rigidly specified. Never assume that a particular set of options corresponds to a particular format. The phrasing of the overall format (encompassing all requested components) might vary across browsers, or even across browser versions. Individual components’ formats are unspecified: a short-format weekday might be “S”, “Sa”, or “Sat”. The Intl API isn’t intended to expose exactly specified behavior.

Date/time formatting


The primary options properties for date/time formatting are as follows:

weekday, era
"narrow", "short", or "long". (era refers to typically longer-than-year divisions in a calendar system: BC/AD, the current Japanese emperor’s reign, or others.)
"2-digit", "numeric", "narrow", "short", or "long"
hour, minute, second
"2-digit" or "numeric"
"short" or "long"
Case-insensitive "UTC" will format with respect to UTC. Values like "CEST" and "America/New_York" don’t have to be supported, and they don’t currently work in Firefox.

The values don’t map to particular formats: remember, the Intl API almost never specifies exact behavior. But the intent is that "narrow", "short", and "long" produce output of corresponding size — “S” or “Sa”, “Sat”, and “Saturday”, for example. (Output may be ambiguous: Saturday and Sunday both could produce “S”.) "2-digit" and "numeric" map to two-digit number strings or full-length numeric strings: “70” and “1970”, for example.

The final used options are largely the requested options. However, if you don’t specifically request any weekday/year/month/day/hour/minute/second, then year/month/day will be added to your provided options.

Beyond these basic options are a few special options:

Specifies whether hours will be in 12-hour or 24-hour format. The default is typically locale-dependent. (Details such as whether midnight is zero-based or twelve-based and whether leading zeroes are present are also locale-dependent.)

There are also two special properties, localeMatcher (taking either "lookup" or "best fit") and formatMatcher (taking either "basic" or "best fit"), each defaulting to "best fit". These affect how the right locale and format are selected. The use cases for these are somewhat esoteric, so you should probably ignore them.

Locale-centric options

DateTimeFormat also allows formatting using customized calendaring and numbering systems. These details are effectively part of the locale, so they’re specified in the Unicode extension in the language tag.

For example, Thai as spoken in Thailand has the language tag th-TH. Recall that a Unicode extension has the format -u-key1-value1-key2-value2.... The calendaring system key is ca, and the numbering system key is nu. The Thai numbering system has the value thai, and the Chinese calendaring system has the value chinese. Thus to format dates in this overall manner, we tack a Unicode extension containing both these key/value pairs onto the end of the language tag: th-TH-u-ca-chinese-nu-thai.

For more information on the various calendaring and numbering systems, see the full DateTimeFormat documentation.


After creating a DateTimeFormat object, the next step is to use it to format dates via the handy format() function. Conveniently, this function is a bound function: you don’t have to call it on the DateTimeFormat directly. Then provide it a timestamp or Date object.

Putting it all together, here are some examples of how to create DateTimeFormat options for particular uses, with current behavior in Firefox.

var msPerDay = 24 * 60 * 60 * 1000;

// July 17, 2014 00:00:00 UTC.
var july172014 = new Date(msPerDay * (44 * 365 + 11 + 197));

Let’s format a date for English as used in the United States. Let’s include two-digit month/day/year, plus two-digit hours/minutes, and a short time zone to clarify that time. (The result would obviously be different in another time zone.)

var options =
  { year: "2-digit", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit",
    timeZoneName: "short" };
var americanDateTime =
  new Intl.DateTimeFormat("en-US", options).format;

print(americanDateTime(july172014)); // 07/16/14, 5:00 PM PDT

Or let’s do something similar for Portuguese — ideally as used in Brazil, but in a pinch Portugal works. Let’s go for a little longer format, with full year and spelled-out month, but make it UTC for portability.

var options =
  { year: "numeric", month: "long", day: "numeric",
    hour: "2-digit", minute: "2-digit",
    timeZoneName: "short", timeZone: "UTC" };
var portugueseTime =
  new Intl.DateTimeFormat(["pt-BR", "pt-PT"], options);

// 17 de julho de 2014 00:00 GMT

How about a compact, UTC-formatted weekly Swiss train schedule? We’ll try the official languages from most to least popular to choose the one that’s most likely to be readable.

var swissLocales = ["de-CH", "fr-CH", "it-CH", "rm-CH"];
var options =
  { weekday: "short",
    hour: "numeric", minute: "numeric",
    timeZone: "UTC", timeZoneName: "short" };
var swissTime =
  new Intl.DateTimeFormat(swissLocales, options).format;

print(swissTime(july172014)); // Do. 00:00 GMT

Or let’s try a date in descriptive text by a painting in a Japanese museum, using the Japanese calendar with year and era:

var jpYearEra =
  new Intl.DateTimeFormat("ja-JP-u-ca-japanese",
                          { year: "numeric", era: "long" });

print(jpYearEra.format(july172014)); // 平成26年

And for something completely different, a longer date for use in Thai as used in Thailand — but using the Thai numbering system and Chinese calendar. (Quality implementations such as Firefox’s would treat plain th-TH as th-TH-u-ca-buddhist-nu-latn, imputing Thailand’s typical Buddhist calendar system and Latin 0-9 numerals.)

var options =
  { year: "numeric", month: "long", day: "numeric" };
var thaiDate =
  new Intl.DateTimeFormat("th-TH-u-nu-thai-ca-chinese", options);

print(thaiDate.format(july172014)); // ๒๐ 6 ๓๑

Calendar and numbering system bits aside, it’s relatively simple. Just pick your components and their lengths.

Number formatting


The primary options properties for number formatting are as follows:

"currency", "percent", or "decimal" (the default) to format a value of that kind.
A three-letter currency code, e.g. USD or CHF. Required if style is "currency", otherwise meaningless.
"code", "symbol", or "name", defaulting to "symbol". "code" will use the three-letter currency code in the formatted string. "symbol" will use a currency symbol such as $ or £. "name" typically uses some sort of spelled-out version of the currency. (Firefox currently only supports "symbol", but this will be fixed soon.)
An integer from 1 to 21 (inclusive), defaulting to 1. The resulting string is front-padded with zeroes until its integer component contains at least this many digits. (For example, if this value were 2, formatting 3 might produce “03”.)
minimumFractionDigits, maximumFractionDigits
Integers from 0 to 20 (inclusive). The resulting string will have at least minimumFractionDigits, and no more than maximumFractionDigits, fractional digits. The default minimum is currency-dependent (usually 2, rarely 0 or 3) if style is "currency", otherwise 0. The default maximum is 0 for percents, 3 for decimals, and currency-dependent for currencies.
minimumSignificantDigits, maximumSignificantDigits
Integers from 1 to 21 (inclusive). If present, these override the integer/fraction digit control above to determine the minimum/maximum significant figures in the formatted number string, as determined in concert with the number of decimal places required to accurately specify the number. (Note that in a multiple of 10 the significant digits may be ambiguous, as in “100” with its one, two, or three significant digits.)
Boolean (defaulting to true) determining whether the formatted string will contain grouping separators (e.g. “,” as English thousands separator).

NumberFormat also recognizes the esoteric, mostly ignorable localeMatcher property.

Locale-centric options

Just as DateTimeFormat supported custom numbering systems in the Unicode extension using the nu key, so too does NumberFormat. For example, the language tag for Chinese as used in China is zh-CN. The value for the Han decimal numbering system is hanidec. To format numbers for these systems, we tack a Unicode extension onto the language tag: zh-CN-u-nu-hanidec.

For complete information on specifying the various numbering systems, see the full NumberFormat documentation.


NumberFormat objects have a format function property just as DateTimeFormat objects do. And as there, the format function is a bound function that may be used in isolation from the NumberFormat.

Here are some examples of how to create NumberFormat options for particular uses, with Firefox’s behavior. First let’s format some money for use in Chinese as used in China, specifically using Han decimal numbers (instead of much more common Latin numbers). Select the "currency" style, then use the code for Chinese renminbi (yuan), grouping by default, with the usual number of fractional digits.

var hanDecimalRMBInChina =
  new Intl.NumberFormat("zh-CN-u-nu-hanidec",
                        { style: "currency", currency: "CNY" });

print(hanDecimalRMBInChina.format(1314.25)); // ¥ 一,三一四.二五

Or let’s format a United States-style gas price, with its peculiar thousandths-place 9, for use in English as used in the United States.

var gasPrice =
  new Intl.NumberFormat("en-US",
                        { style: "currency", currency: "USD",
                          minimumFractionDigits: 3 });

print(gasPrice.format(5.259)); // $5.259

Or let’s try a percentage in Arabic, meant for use in Egypt. Make sure the percentage has at least two fractional digits. (Note that this and all the other RTL examples may appear with different ordering in RTL context, e.g. ٤٣٫٨٠٪ instead of ٤٣٫٨٠٪.)

var arabicPercent =
  new Intl.NumberFormat("ar-EG",
                        { style: "percent",
                          minimumFractionDigits: 2 }).format;

print(arabicPercent(0.438)); // ٤٣٫٨٠٪

Or suppose we’re formatting for Persian as used in Afghanistan, and we want at least two integer digits and no more than two fractional digits.

var persianDecimal =
  new Intl.NumberFormat("fa-AF",
                        { minimumIntegerDigits: 2,
                          maximumFractionDigits: 2 });

print(persianDecimal.format(3.1416)); // ۰۳٫۱۴

Finally, let’s format an amount of Bahraini dinars, for Arabic as used in Bahrain. Unusually compared to most currencies, Bahraini dinars divide into thousandths (fils), so our number will have three places. (Again note that apparent visual ordering should be taken with a grain of salt.)

var bahrainiDinars =
  new Intl.NumberFormat("ar-BH",
                        { style: "currency", currency: "BHD" });

print(bahrainiDinars.format(3.17)); // د.ب.‏ ٣٫١٧٠



The primary options properties for collation are as follows:

"sort" or "search" (defaulting to "sort"), specifying the intended use of this Collator. (A search collator might want to consider more strings equivalent than a sort collator would.)
"base", "accent", "case", or "variant". This affects how sensitive the collator is to characters that have the same “base letter” but have different accents/diacritics and/or case. (Base letters are locale-dependent: “a” and “ä” have the same base letter in German but are different letters in Swedish.) "base" sensitivity considers only the base letter, ignoring modifications (so for German “a”, “A”, and “ä” are considered the same). "accent" considers the base letter and accents but ignores case (so for German “a” and “A” are the same, but “ä” differs from both). "case" considers the base letter and case but ignores accents (so for German “a” and “ä” are the same, but “A” differs from both). Finally, "variant" considers base letter, accents, and case (so for German “a”, “ä, “ä” and “A” all differ). If usage is "sort", the default is "variant"; otherwise it’s locale-dependent.
Boolean (defaulting to false) determining whether complete numbers embedded in strings are considered when sorting. For example, numeric sorting might produce "F-4 Phantom II", "F-14 Tomcat", "F-35 Lightning II"; non-numeric sorting might produce "F-14 Tomcat", "F-35 Lightning II", "F-4 Phantom II".
"upper", "lower", or "false" (the default). Determines how case is considered when sorting: "upper" places uppercase letters first ("B", "a", "c"), "lower" places lowercase first ("a", "c", "B"), and "false" ignores case entirely ("a", "B", "c"). (Note: Firefox currently ignores this property.)
Boolean (defaulting to false) determining whether to ignore embedded punctuation when performing the comparison (for example, so that "biweekly" and "bi-weekly" compare equivalent).

And there’s that localeMatcher property that you can probably ignore.

Locale-centric options

The main Collator option specified as part of the locale’s Unicode extension is co, selecting the kind of sorting to perform: phone book (phonebk), dictionary (dict), and many others.

Additionally, the keys kn and kf may, optionally, duplicate the numeric and caseFirst properties of the options object. But they’re not guaranteed to be supported in the language tag, and options is much clearer than language tag components. So it’s best to only adjust these options through options.

These key-value pairs are included in the Unicode extension the same way they’ve been included for DateTimeFormat and NumberFormat; refer to those sections for how to specify these in a language tag.


Collator objects have a compare function property. This function accepts two arguments x and y and returns a number less than zero if x compares less than y, 0 if x compares equal to y, or a number greater than zero if x compares greater than y. As with the format functions, compare is a bound function that may be extracted for standalone use.

Let’s try sorting a few German surnames, for use in German as used in Germany. There are actually two different sort orders in German, phonebook and dictionary. Phonebook sort emphasizes sound, and it’s as if “ä”, “ö”, and so on were expanded to “ae”, “oe”, and so on prior to sorting.

var names =
  ["Hochberg", "Hönigswald", "Holzman"];

var germanPhonebook = new Intl.Collator("de-DE-u-co-phonebk");

// as if sorting ["Hochberg", "Hoenigswald", "Holzman"]:
//   Hochberg, Hönigswald, Holzman
print(names.sort(germanPhonebook.compare).join(", "));

Some German words conjugate with extra umlauts, so in dictionaries it’s sensible to order ignoring umlauts (except when ordering words differing only by umlauts: schon before schön).

var germanDictionary = new Intl.Collator("de-DE-u-co-dict");

// as if sorting ["Hochberg", "Honigswald", "Holzman"]:
//   Hochberg, Holzman, Hönigswald
print(names.sort(germanDictionary.compare).join(", "));

Or let’s sort a list Firefox versions with various typos (different capitalizations, random accents and diacritical marks, extra hyphenation), in English as used in the United States. We want to sort respecting version number, so do a numeric sort so that numbers in the strings are compared, not considered character-by-character.

var firefoxen =
  ["FireFøx 3.6",
   "Fire-fox 1.0",
   "Firefox 29",
   "FÍrefox 3.5",
   "Fírefox 18"];

var usVersion =
  new Intl.Collator("en-US",
                    { sensitivity: "base",
                      numeric: true,
                      ignorePunctuation: true });

// Fire-fox 1.0, FÍrefox 3.5, FireFøx 3.6, Fírefox 18, Firefox 29
print(firefoxen.sort(usVersion.compare).join(", "));

Last, let’s do some locale-aware string searching that ignores case and accents, again in English as used in the United States.

// Comparisons work with both composed and decomposed forms.
var decoratedBrowsers =
   "A\u0362maya",  // A͢maya
   "CH\u035Brôme", // CH͛rôme
   "o\u0323pERA",  // ọpERA
   "I\u0352E",     // I͒E

var fuzzySearch =
  new Intl.Collator("en-US",
                    { usage: "search", sensitivity: "base" });

function findBrowser(browser)
  function cmp(other)
    return fuzzySearch.compare(browser, other) === 0;
  return cmp;

print(decoratedBrowsers.findIndex(findBrowser("Firêfox"))); // 2
print(decoratedBrowsers.findIndex(findBrowser("Safåri")));  // 3
print(decoratedBrowsers.findIndex(findBrowser("Ãmaya")));   // 0
print(decoratedBrowsers.findIndex(findBrowser("Øpera")));   // 4
print(decoratedBrowsers.findIndex(findBrowser("Chromè")));  // 1
print(decoratedBrowsers.findIndex(findBrowser("IË")));      // 5

Odds and ends

It may be useful to determine whether support for some operation is provided for particular locales, or to determine whether a locale is supported. Intl provides supportedLocales() functions on each constructor, and resolvedOptions() functions on each prototype, to expose this information.

var navajoLocales =
  Intl.Collator.supportedLocalesOf(["nv"], { usage: "sort" });
print(navajoLocales.length > 0
      ? "Navajo collation supported"
      : "Navajo collation not supported");

var germanFakeRegion =
  new Intl.DateTimeFormat("de-XX", { timeZone: "UTC" });
var usedOptions = germanFakeRegion.resolvedOptions();
print(usedOptions.locale);   // de
print(usedOptions.timeZone); // UTC

Legacy behavior

The ES5 toLocaleString-style and localeCompare functions previously had no particular semantics, accepted no particular options, and were largely useless. So the i18n API reformulates them in terms of Intl operations. Each method now accepts additional trailing locales and options arguments, interpreted just as the Intl constructors would do. (Except that for toLocaleTimeString and toLocaleDateString, different default components are used if options aren’t provided.)

For brief use where precise behavior doesn’t matter, the old methods are fine to use. But if you need more control or are formatting or comparing many times, it’s best to use the Intl primitives directly.


Internationalization is a fascinating topic whose complexity is bounded only by the varied nature of human communication. The Internationalization API addresses a small but quite useful portion of that complexity, making it easier to produce locale-sensitive web applications. Go use it!

(And a special thanks to Norbert Lindenberg, Anas El Husseini, Simon Montagu, Gary Kwong, Shu-yu Guo, Ehsan Akhgari, the people of #mozilla.de, and anyone I may have forgotten [sorry!] who provided feedback on this article or assisted me in producing and critiquing the examples. The English and German examples were the limit of my knowledge, and I’d have been completely lost on the other examples without their assistance. Blame all remaining errors on me. Thanks again!)

(and to reiterate: comment on the Hacks post if you have anything to say)

« NewerOlder »