Welsh Cymraeg

BCP 47 language tag: cy

Lokalized supports cardinality, cardinality range, and ordinality rules for Welsh.

Welsh is most often spoken in the United Kingdom and Argentina.

Cardinalities

  • CARDINALITY_ZERO

    Matches n = 0

    Example Integers: 0

    Example Decimals: 0.0, 0.00, 0.000, 0.0000

  • CARDINALITY_ONE

    Matches n = 1

    Example Integers: 1

    Example Decimals: 1.0, 1.00, 1.000, 1.0000

  • CARDINALITY_TWO

    Matches n = 2

    Example Integers: 2

    Example Decimals: 2.0, 2.00, 2.000, 2.0000

  • CARDINALITY_FEW

    Matches n = 3

    Example Integers: 3

    Example Decimals: 3.0, 3.00, 3.000, 3.0000

  • CARDINALITY_MANY

    Matches n = 6

    Example Integers: 6

    Example Decimals: 6.0, 6.00, 6.000, 6.0000

  • CARDINALITY_OTHER

    Matches all other values

    Example Integers: 4, 5, 7, 20, 100, 1000, 10000, 100000, 1000000, …

    Example Decimals: 0.1, 0.9, 1.1, 1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …

Cardinality example

This localized strings example uses complete, page-local CLDR minimal-pair patterns to demonstrate every cardinality category defined for the locale. Minimal-pair text contrasts grammatical forms; it is language-reference data, not reusable application copy.

Source: Unicode CLDR 48.2 cy locale data.

Sample localized strings file
cy.json Localized Strings File (Welsh)
{
  "Cardinality.MinimalPair": {
    "commentary": "Demonstrates every CLDR cardinal category defined for this locale.",
    "translation": "{{cardinalityExample}}",
    "placeholders": {
      "cardinalityExample": {
        "value": "cardinalityValue",
        "translations": {
          "CARDINALITY_ZERO": "{{formattedCardinalityValue}} cŵn, {{formattedCardinalityValue}} cathod",
          "CARDINALITY_ONE": "{{formattedCardinalityValue}} ci, {{formattedCardinalityValue}} gath",
          "CARDINALITY_TWO": "{{formattedCardinalityValue}} gi, {{formattedCardinalityValue}} gath",
          "CARDINALITY_FEW": "{{formattedCardinalityValue}} chi, {{formattedCardinalityValue}} cath",
          "CARDINALITY_MANY": "{{formattedCardinalityValue}} chi, {{formattedCardinalityValue}} chath",
          "CARDINALITY_OTHER": "{{formattedCardinalityValue}} ci, {{formattedCardinalityValue}} cath"
        }
      }
    }
  }
}
Usage and expected results

JavaScript

The JavaScript code passes each raw number for cardinality selection and formats its display value separately with Intl.NumberFormat.

import { cardinalityForNumber, createStrings } from "lokalized";
import { readStringsFromDirectory } from "lokalized/node";

const locale = "cy";

const strings = createStrings({
  localizedStringSupplier: () => readStringsFromDirectory("strings").catalogs,
  fallbackLocale: locale,
  localeSupplier: () => locale,
});

const numberFormat = new Intl.NumberFormat(locale);

// Raw numbers select language forms; Intl.NumberFormat produces the display values

// CARDINALITY_ZERO
const zeroCardinality = 0;
const zeroFormattedCardinalityValue = numberFormat.format(zeroCardinality);

cardinalityForNumber(zeroCardinality, locale).name;   // => "CARDINALITY_ZERO"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: zeroCardinality,
  formattedCardinalityValue: zeroFormattedCardinalityValue,
});   // => "0 cŵn, 0 cathod"

// CARDINALITY_ONE
const oneCardinality = 1;
const oneFormattedCardinalityValue = numberFormat.format(oneCardinality);

cardinalityForNumber(oneCardinality, locale).name;   // => "CARDINALITY_ONE"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: oneCardinality,
  formattedCardinalityValue: oneFormattedCardinalityValue,
});   // => "1 ci, 1 gath"

// CARDINALITY_TWO
const twoCardinality = 2;
const twoFormattedCardinalityValue = numberFormat.format(twoCardinality);

cardinalityForNumber(twoCardinality, locale).name;   // => "CARDINALITY_TWO"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: twoCardinality,
  formattedCardinalityValue: twoFormattedCardinalityValue,
});   // => "2 gi, 2 gath"

// CARDINALITY_FEW
const fewCardinality = 3;
const fewFormattedCardinalityValue = numberFormat.format(fewCardinality);

cardinalityForNumber(fewCardinality, locale).name;   // => "CARDINALITY_FEW"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: fewCardinality,
  formattedCardinalityValue: fewFormattedCardinalityValue,
});   // => "3 chi, 3 cath"

// CARDINALITY_MANY
const manyCardinality = 6;
const manyFormattedCardinalityValue = numberFormat.format(manyCardinality);

cardinalityForNumber(manyCardinality, locale).name;   // => "CARDINALITY_MANY"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: manyCardinality,
  formattedCardinalityValue: manyFormattedCardinalityValue,
});   // => "6 chi, 6 chath"

// CARDINALITY_OTHER
const otherCardinality = 4;
const otherFormattedCardinalityValue = numberFormat.format(otherCardinality);

cardinalityForNumber(otherCardinality, locale).name;   // => "CARDINALITY_OTHER"
strings.get("Cardinality.MinimalPair", {
  cardinalityValue: otherCardinality,
  formattedCardinalityValue: otherFormattedCardinalityValue,
});   // => "4 ci, 4 cath"

Java

The Java code passes each raw number for cardinality selection and formats its display value separately with NumberFormat.

Locale locale = Locale.forLanguageTag("cy");

Strings strings = Strings.withFallbackLocale(locale)
  .localizedStringSupplier(() -> LocalizedStringLoader.loadFromFilesystem(Paths.get("strings")))
  .localeSupplier((matcher) -> matcher.bestMatchFor(locale))
  .build();

NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);

// Raw numbers select language forms; NumberFormat produces the display values

// CARDINALITY_ZERO
Number zeroCardinality = 0;
String zeroFormattedCardinalityValue = numberFormat.format(zeroCardinality);

assertEquals(Cardinality.ZERO, Cardinality.forNumber(zeroCardinality, locale));

// Expected: 0 cŵn, 0 cathod
assertEquals(
  zeroFormattedCardinalityValue + " cŵn, " + zeroFormattedCardinalityValue + " cathod",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", zeroCardinality,
      "formattedCardinalityValue", zeroFormattedCardinalityValue
    )
  )
);

// CARDINALITY_ONE
Number oneCardinality = 1;
String oneFormattedCardinalityValue = numberFormat.format(oneCardinality);

assertEquals(Cardinality.ONE, Cardinality.forNumber(oneCardinality, locale));

// Expected: 1 ci, 1 gath
assertEquals(
  oneFormattedCardinalityValue + " ci, " + oneFormattedCardinalityValue + " gath",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", oneCardinality,
      "formattedCardinalityValue", oneFormattedCardinalityValue
    )
  )
);

// CARDINALITY_TWO
Number twoCardinality = 2;
String twoFormattedCardinalityValue = numberFormat.format(twoCardinality);

assertEquals(Cardinality.TWO, Cardinality.forNumber(twoCardinality, locale));

// Expected: 2 gi, 2 gath
assertEquals(
  twoFormattedCardinalityValue + " gi, " + twoFormattedCardinalityValue + " gath",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", twoCardinality,
      "formattedCardinalityValue", twoFormattedCardinalityValue
    )
  )
);

// CARDINALITY_FEW
Number fewCardinality = 3;
String fewFormattedCardinalityValue = numberFormat.format(fewCardinality);

assertEquals(Cardinality.FEW, Cardinality.forNumber(fewCardinality, locale));

// Expected: 3 chi, 3 cath
assertEquals(
  fewFormattedCardinalityValue + " chi, " + fewFormattedCardinalityValue + " cath",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", fewCardinality,
      "formattedCardinalityValue", fewFormattedCardinalityValue
    )
  )
);

// CARDINALITY_MANY
Number manyCardinality = 6;
String manyFormattedCardinalityValue = numberFormat.format(manyCardinality);

assertEquals(Cardinality.MANY, Cardinality.forNumber(manyCardinality, locale));

// Expected: 6 chi, 6 chath
assertEquals(
  manyFormattedCardinalityValue + " chi, " + manyFormattedCardinalityValue + " chath",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", manyCardinality,
      "formattedCardinalityValue", manyFormattedCardinalityValue
    )
  )
);

// CARDINALITY_OTHER
Number otherCardinality = 4;
String otherFormattedCardinalityValue = numberFormat.format(otherCardinality);

assertEquals(Cardinality.OTHER, Cardinality.forNumber(otherCardinality, locale));

// Expected: 4 ci, 4 cath
assertEquals(
  otherFormattedCardinalityValue + " ci, " + otherFormattedCardinalityValue + " cath",
  strings.get(
    "Cardinality.MinimalPair",
    Map.of(
      "cardinalityValue", otherCardinality,
      "formattedCardinalityValue", otherFormattedCardinalityValue
    )
  )
);

Swift

Swift uses pinned CLDR rules for raw numeric values. The display strings in these assertions are fixed examples; applications format display values separately with Foundation. Default .rtlLocales bidi isolation adds FSI/PDI to interpolated values in right-to-left output.

import Foundation
import Lokalized

let locale = try LocaleTag("cy")
let files = try LocalizedStringLoader.loadFromDirectory(URL(fileURLWithPath: "strings"))
let catalogs = files.mapValues { LocalizedCatalog(strings: $0.strings) }

let strings = try DefaultStrings(configuration: StringsConfiguration(
  localizedStringSupplier: { catalogs }, localeSupplier: { _ in locale },
  fallbackLocale: locale
))

// CARDINALITY_ZERO
let number1 = try NumericValue.forDecimal("0")
let category1 = try Cardinality.forNumber(number1, locale: locale.tag)
assert(category1 == .zero)
// This fixed display value makes the translation assertion reproducible.
let display1 = "0"
let message1 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number1),
  "formattedCardinalityValue": .text(display1),
])
assert(ExactString(message1) == ExactString("0 cŵn, 0 cathod"))

// CARDINALITY_ONE
let number2 = try NumericValue.forDecimal("1")
let category2 = try Cardinality.forNumber(number2, locale: locale.tag)
assert(category2 == .one)
// This fixed display value makes the translation assertion reproducible.
let display2 = "1"
let message2 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number2),
  "formattedCardinalityValue": .text(display2),
])
assert(ExactString(message2) == ExactString("1 ci, 1 gath"))

// CARDINALITY_TWO
let number3 = try NumericValue.forDecimal("2")
let category3 = try Cardinality.forNumber(number3, locale: locale.tag)
assert(category3 == .two)
// This fixed display value makes the translation assertion reproducible.
let display3 = "2"
let message3 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number3),
  "formattedCardinalityValue": .text(display3),
])
assert(ExactString(message3) == ExactString("2 gi, 2 gath"))

// CARDINALITY_FEW
let number4 = try NumericValue.forDecimal("3")
let category4 = try Cardinality.forNumber(number4, locale: locale.tag)
assert(category4 == .few)
// This fixed display value makes the translation assertion reproducible.
let display4 = "3"
let message4 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number4),
  "formattedCardinalityValue": .text(display4),
])
assert(ExactString(message4) == ExactString("3 chi, 3 cath"))

// CARDINALITY_MANY
let number5 = try NumericValue.forDecimal("6")
let category5 = try Cardinality.forNumber(number5, locale: locale.tag)
assert(category5 == .many)
// This fixed display value makes the translation assertion reproducible.
let display5 = "6"
let message5 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number5),
  "formattedCardinalityValue": .text(display5),
])
assert(ExactString(message5) == ExactString("6 chi, 6 chath"))

// CARDINALITY_OTHER
let number6 = try NumericValue.forDecimal("4")
let category6 = try Cardinality.forNumber(number6, locale: locale.tag)
assert(category6 == .other)
// This fixed display value makes the translation assertion reproducible.
let display6 = "4"
let message6 = try strings.get("Cardinality.MinimalPair", placeholders: [
  "cardinalityValue": .number(number6),
  "formattedCardinalityValue": .text(display6),
])
assert(ExactString(message6) == ExactString("4 ci, 4 cath"))

Cardinality Ranges

These are CLDR's explicit range mappings. For any start/end pair not listed, the range falls back to the ending cardinality.

  • CARDINALITY_ZERO - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • CARDINALITY_ZERO - CARDINALITY_TWO

    Matches CARDINALITY_TWO

  • CARDINALITY_ZERO - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_ZERO - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_ZERO - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_ONE - CARDINALITY_TWO

    Matches CARDINALITY_TWO

  • CARDINALITY_ONE - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_ONE - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_ONE - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_TWO - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_TWO - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_TWO - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_FEW - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_FEW - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_MANY - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_OTHER - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • CARDINALITY_OTHER - CARDINALITY_TWO

    Matches CARDINALITY_TWO

  • CARDINALITY_OTHER - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_OTHER - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_OTHER - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

Cardinality range example

The start and end cardinalities select the locale's CLDR range result. The selected form wraps a separately formatted display range.

Sample localized strings file
cy.json Localized Strings File (Welsh)
{
  "Delivery.Window": {
    "commentary": "Estimated delivery window, expressed as a range of days.",
    "translation": "{{window}}",
    "placeholders": {
      "window": {
        "range": {
          "start": "minDays",
          "end": "maxDays"
        },
        "translations": {
          "CARDINALITY_ZERO": "{{formattedDayRange}} diwrnod",
          "CARDINALITY_ONE": "{{formattedDayRange}} diwrnod",
          "CARDINALITY_TWO": "{{formattedDayRange}} ddiwrnod",
          "CARDINALITY_FEW": "{{formattedDayRange}} diwrnod",
          "CARDINALITY_MANY": "{{formattedDayRange}} diwrnod",
          "CARDINALITY_OTHER": "{{formattedDayRange}} diwrnod"
        }
      }
    }
  }
}
Usage and expected results

JavaScript

The JavaScript code passes raw range endpoints for selection, formats both endpoints separately with Intl.NumberFormat, and joins them for display; choose range punctuation appropriate to your application. Range rules are an optional import, lokalized/data/ranges.

import { cardinalityForNumber, createStrings } from "lokalized";
import { cardinalRangeData, cardinalityForRange } from "lokalized/data/ranges";
import { readStringsFromDirectory } from "lokalized/node";

const locale = "cy";

const strings = createStrings({
  localizedStringSupplier: () => readStringsFromDirectory("strings").catalogs,
  fallbackLocale: locale,
  localeSupplier: () => locale,
  pluralData: { ranges: cardinalRangeData },
});

const numberFormat = new Intl.NumberFormat(locale);

// Raw numbers select language forms; Intl.NumberFormat produces the display values

// CARDINALITY_ONE range
const minDays = 0;
const formattedMinDays = numberFormat.format(minDays);
const maxDays = 1;
const formattedMaxDays = numberFormat.format(maxDays);
const formattedDayRange = formattedMinDays + "-" + formattedMaxDays;

cardinalityForRange(
  cardinalityForNumber(minDays, locale),
  cardinalityForNumber(maxDays, locale),
  locale
).name;   // => "CARDINALITY_ONE"
strings.get("Delivery.Window", { minDays, maxDays, formattedDayRange });   // => "0-1 diwrnod"

Java

The Java code passes raw range endpoints for selection. Because NumberFormat formats one number at a time, it formats both endpoints separately and joins them for display; choose range punctuation appropriate to your application.

Locale locale = Locale.forLanguageTag("cy");

Strings strings = Strings.withFallbackLocale(locale)
  .localizedStringSupplier(() -> LocalizedStringLoader.loadFromFilesystem(Paths.get("strings")))
  .localeSupplier((matcher) -> matcher.bestMatchFor(locale))
  .build();

NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);

// Raw numbers select language forms; NumberFormat produces the display values

// CARDINALITY_ONE range
Number minDays = 0;
String formattedMinDays = numberFormat.format(minDays);
Number maxDays = 1;
String formattedMaxDays = numberFormat.format(maxDays);
String formattedDayRange = formattedMinDays + "-" + formattedMaxDays;

assertEquals(Cardinality.ONE, Cardinality.forRange(
  Cardinality.forNumber(minDays, locale),
  Cardinality.forNumber(maxDays, locale),
  locale
));

// Expected: 0-1 diwrnod
assertEquals(formattedDayRange + " diwrnod", strings.get(
  "Delivery.Window",
  Map.of("minDays", minDays, "maxDays", maxDays, "formattedDayRange", formattedDayRange)
));

Swift

Swift uses pinned CLDR rules for raw numeric values. The display strings in these assertions are fixed examples; applications format display values separately with Foundation. Default .rtlLocales bidi isolation adds FSI/PDI to interpolated values in right-to-left output.

import Foundation
import Lokalized

let locale = try LocaleTag("cy")
let files = try LocalizedStringLoader.loadFromDirectory(URL(fileURLWithPath: "strings"))
let catalogs = files.mapValues { LocalizedCatalog(strings: $0.strings) }

let strings = try DefaultStrings(configuration: StringsConfiguration(
  localizedStringSupplier: { catalogs }, localeSupplier: { _ in locale },
  fallbackLocale: locale
))

let start = try NumericValue.forDecimal("0")
let end = try NumericValue.forDecimal("1")
let startForm = try Cardinality.forNumber(start, locale: locale.tag)
let endForm = try Cardinality.forNumber(end, locale: locale.tag)
let rangeForm = try Cardinality.forRange(startForm, endForm, locale: locale.tag)
assert(rangeForm == .one)
let message1 = try strings.get("Delivery.Window", placeholders: [
  "minDays": .number(start),
  "maxDays": .number(end),
  "formattedDayRange": .text("0-1"),
])
assert(ExactString(message1) == ExactString("0-1 diwrnod"))

Ordinalities

  • ORDINALITY_ZERO

    Matches n = 0,7,8,9

    Example Integers: 0, 7, 9

  • ORDINALITY_ONE

    Matches n = 1

    Example Integers: 1

  • ORDINALITY_TWO

    Matches n = 2

    Example Integers: 2

  • ORDINALITY_FEW

    Matches n = 3,4

    Example Integers: 3, 4

  • ORDINALITY_MANY

    Matches n = 5,6

    Example Integers: 5, 6

  • ORDINALITY_OTHER

    Matches all other values

    Example Integers: 10, 25, 100, 1000, 10000, 100000, 1000000, …

Ordinality example

This localized strings example uses complete CLDR minimal-pair patterns to demonstrate every ordinality category defined for the locale.

Source: Unicode CLDR 48.2 cy locale data.

Sample localized strings file
cy.json Localized Strings File (Welsh)
{
  "Ordinal.MinimalPair": {
    "commentary": "Demonstrates every CLDR ordinal category defined for this locale.",
    "translation": "{{ordinalExample}}",
    "placeholders": {
      "ordinalExample": {
        "value": "ordinalValue",
        "translations": {
          "ORDINALITY_ZERO": "{{formattedOrdinalValue}}fed ci",
          "ORDINALITY_ONE": "ci {{formattedOrdinalValue}}af",
          "ORDINALITY_TWO": "{{formattedOrdinalValue}}il gi",
          "ORDINALITY_FEW": "{{formattedOrdinalValue}}ydd ci",
          "ORDINALITY_MANY": "{{formattedOrdinalValue}}ed ci",
          "ORDINALITY_OTHER": "ci rhif {{formattedOrdinalValue}}"
        }
      }
    }
  }
}
Usage and expected results

JavaScript

The JavaScript code passes each raw number to ordinalityForNumber(...) and formats its display value separately with Intl.NumberFormat. Ordinal rules are an optional import, lokalized/data/ordinal.

import { createStrings } from "lokalized";
import { ordinalData, ordinalityForNumber } from "lokalized/data/ordinal";
import { readStringsFromDirectory } from "lokalized/node";

const locale = "cy";

const strings = createStrings({
  localizedStringSupplier: () => readStringsFromDirectory("strings").catalogs,
  fallbackLocale: locale,
  localeSupplier: () => locale,
  pluralData: { ordinal: ordinalData },
});

const numberFormat = new Intl.NumberFormat(locale);

// Raw numbers select language forms; Intl.NumberFormat produces the display values

// ORDINALITY_ZERO
const zeroOrdinalValue = 0;
const zeroFormattedOrdinalValue = numberFormat.format(zeroOrdinalValue);

ordinalityForNumber(zeroOrdinalValue, locale).name;   // => "ORDINALITY_ZERO"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: zeroOrdinalValue,
  formattedOrdinalValue: zeroFormattedOrdinalValue,
});   // => "0fed ci"

// ORDINALITY_ONE
const oneOrdinalValue = 1;
const oneFormattedOrdinalValue = numberFormat.format(oneOrdinalValue);

ordinalityForNumber(oneOrdinalValue, locale).name;   // => "ORDINALITY_ONE"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: oneOrdinalValue,
  formattedOrdinalValue: oneFormattedOrdinalValue,
});   // => "ci 1af"

// ORDINALITY_TWO
const twoOrdinalValue = 2;
const twoFormattedOrdinalValue = numberFormat.format(twoOrdinalValue);

ordinalityForNumber(twoOrdinalValue, locale).name;   // => "ORDINALITY_TWO"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: twoOrdinalValue,
  formattedOrdinalValue: twoFormattedOrdinalValue,
});   // => "2il gi"

// ORDINALITY_FEW
const fewOrdinalValue = 3;
const fewFormattedOrdinalValue = numberFormat.format(fewOrdinalValue);

ordinalityForNumber(fewOrdinalValue, locale).name;   // => "ORDINALITY_FEW"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: fewOrdinalValue,
  formattedOrdinalValue: fewFormattedOrdinalValue,
});   // => "3ydd ci"

// ORDINALITY_MANY
const manyOrdinalValue = 5;
const manyFormattedOrdinalValue = numberFormat.format(manyOrdinalValue);

ordinalityForNumber(manyOrdinalValue, locale).name;   // => "ORDINALITY_MANY"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: manyOrdinalValue,
  formattedOrdinalValue: manyFormattedOrdinalValue,
});   // => "5ed ci"

// ORDINALITY_OTHER
const otherOrdinalValue = 10;
const otherFormattedOrdinalValue = numberFormat.format(otherOrdinalValue);

ordinalityForNumber(otherOrdinalValue, locale).name;   // => "ORDINALITY_OTHER"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: otherOrdinalValue,
  formattedOrdinalValue: otherFormattedOrdinalValue,
});   // => "ci rhif 10"

Java

The Java code passes each raw number to Ordinality.forNumber(...) and formats its display value separately with NumberFormat.

Locale locale = Locale.forLanguageTag("cy");

Strings strings = Strings.withFallbackLocale(locale)
  .localizedStringSupplier(() -> LocalizedStringLoader.loadFromFilesystem(Paths.get("strings")))
  .localeSupplier((matcher) -> matcher.bestMatchFor(locale))
  .build();

NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);

// Raw numbers select language forms; NumberFormat produces the display values

// ORDINALITY_ZERO
Number zeroOrdinalValue = 0;
String zeroFormattedOrdinalValue = numberFormat.format(zeroOrdinalValue);

assertEquals(Ordinality.ZERO, Ordinality.forNumber(zeroOrdinalValue, locale));

// Expected: 0fed ci
assertEquals(zeroFormattedOrdinalValue + "fed ci", strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", zeroOrdinalValue, "formattedOrdinalValue", zeroFormattedOrdinalValue)
));

// ORDINALITY_ONE
Number oneOrdinalValue = 1;
String oneFormattedOrdinalValue = numberFormat.format(oneOrdinalValue);

assertEquals(Ordinality.ONE, Ordinality.forNumber(oneOrdinalValue, locale));

// Expected: ci 1af
assertEquals("ci " + oneFormattedOrdinalValue + "af", strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", oneOrdinalValue, "formattedOrdinalValue", oneFormattedOrdinalValue)
));

// ORDINALITY_TWO
Number twoOrdinalValue = 2;
String twoFormattedOrdinalValue = numberFormat.format(twoOrdinalValue);

assertEquals(Ordinality.TWO, Ordinality.forNumber(twoOrdinalValue, locale));

// Expected: 2il gi
assertEquals(twoFormattedOrdinalValue + "il gi", strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", twoOrdinalValue, "formattedOrdinalValue", twoFormattedOrdinalValue)
));

// ORDINALITY_FEW
Number fewOrdinalValue = 3;
String fewFormattedOrdinalValue = numberFormat.format(fewOrdinalValue);

assertEquals(Ordinality.FEW, Ordinality.forNumber(fewOrdinalValue, locale));

// Expected: 3ydd ci
assertEquals(fewFormattedOrdinalValue + "ydd ci", strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", fewOrdinalValue, "formattedOrdinalValue", fewFormattedOrdinalValue)
));

// ORDINALITY_MANY
Number manyOrdinalValue = 5;
String manyFormattedOrdinalValue = numberFormat.format(manyOrdinalValue);

assertEquals(Ordinality.MANY, Ordinality.forNumber(manyOrdinalValue, locale));

// Expected: 5ed ci
assertEquals(manyFormattedOrdinalValue + "ed ci", strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", manyOrdinalValue, "formattedOrdinalValue", manyFormattedOrdinalValue)
));

// ORDINALITY_OTHER
Number otherOrdinalValue = 10;
String otherFormattedOrdinalValue = numberFormat.format(otherOrdinalValue);

assertEquals(Ordinality.OTHER, Ordinality.forNumber(otherOrdinalValue, locale));

// Expected: ci rhif 10
assertEquals("ci rhif " + otherFormattedOrdinalValue, strings.get(
  "Ordinal.MinimalPair",
  Map.of("ordinalValue", otherOrdinalValue, "formattedOrdinalValue", otherFormattedOrdinalValue)
));

Swift

Swift uses pinned CLDR rules for raw numeric values. The display strings in these assertions are fixed examples; applications format display values separately with Foundation. Default .rtlLocales bidi isolation adds FSI/PDI to interpolated values in right-to-left output.

import Foundation
import Lokalized

let locale = try LocaleTag("cy")
let files = try LocalizedStringLoader.loadFromDirectory(URL(fileURLWithPath: "strings"))
let catalogs = files.mapValues { LocalizedCatalog(strings: $0.strings) }

let strings = try DefaultStrings(configuration: StringsConfiguration(
  localizedStringSupplier: { catalogs }, localeSupplier: { _ in locale },
  fallbackLocale: locale
))

let number1 = try NumericValue.forDecimal("0")
let category1 = try Ordinality.forNumber(number1, locale: locale.tag)
assert(category1 == .zero)
let message1 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number1),
  "formattedOrdinalValue": .text("0"),
])
assert(ExactString(message1) == ExactString("0fed ci"))

let number2 = try NumericValue.forDecimal("1")
let category2 = try Ordinality.forNumber(number2, locale: locale.tag)
assert(category2 == .one)
let message2 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number2),
  "formattedOrdinalValue": .text("1"),
])
assert(ExactString(message2) == ExactString("ci 1af"))

let number3 = try NumericValue.forDecimal("2")
let category3 = try Ordinality.forNumber(number3, locale: locale.tag)
assert(category3 == .two)
let message3 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number3),
  "formattedOrdinalValue": .text("2"),
])
assert(ExactString(message3) == ExactString("2il gi"))

let number4 = try NumericValue.forDecimal("3")
let category4 = try Ordinality.forNumber(number4, locale: locale.tag)
assert(category4 == .few)
let message4 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number4),
  "formattedOrdinalValue": .text("3"),
])
assert(ExactString(message4) == ExactString("3ydd ci"))

let number5 = try NumericValue.forDecimal("5")
let category5 = try Ordinality.forNumber(number5, locale: locale.tag)
assert(category5 == .many)
let message5 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number5),
  "formattedOrdinalValue": .text("5"),
])
assert(ExactString(message5) == ExactString("5ed ci"))

let number6 = try NumericValue.forDecimal("10")
let category6 = try Ordinality.forNumber(number6, locale: locale.tag)
assert(category6 == .other)
let message6 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number6),
  "formattedOrdinalValue": .text("10"),
])
assert(ExactString(message6) == ExactString("ci rhif 10"))

Addendum: Language Form Rules

The language form expressions above are specified by Unicode Technical Standard #35 and use the following notation:

  • n absolute value of the source number (integer and decimals)
  • i integer digits of n
  • v number of visible fraction digits in n, with trailing zeros
  • w number of visible fraction digits in n, without trailing zeros
  • f visible fractional digits in n, with trailing zeros
  • t visible fractional digits in n, without trailing zeros
  • c/e compact decimal exponent operands used by some CLDR compact-number rules