Ukrainian українська

BCP 47 language tag: uk

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

Ukrainian is most often spoken in Ukraine, Moldova, Poland, Canada, and Slovakia.

Cardinalities

  • CARDINALITY_ONE

    Matches v = 0 and i % 10 = 1 and i % 100 != 11

    Example Integers: 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …

  • CARDINALITY_FEW

    Matches v = 0 and i % 10 = 2..4 and i % 100 != 12..14

    Example Integers: 2, 4, 22, 24, 32, 34, 42, 44, 52, 54, 62, 102, 1002, …

  • CARDINALITY_MANY

    Matches v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14

    Example Integers: 0, 5, 19, 100, 1000, 10000, 100000, 1000000, …

  • CARDINALITY_OTHER

    Matches all other values

    Example Decimals: 0.0, 1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …

Cardinality example

This localized strings file selects the localized form of “day” for a delivery estimate. Raw numbers choose the CLDR category; values formatted for display are interpolated.

Sample localized strings file
uk.json Localized Strings File (Ukrainian)
{
  "Delivery.LeadTime": {
    "commentary": "Estimated delivery time, expressed in days.",
    "translation": "{{leadTime}}",
    "placeholders": {
      "leadTime": {
        "value": "dayCount",
        "translations": {
          "CARDINALITY_ONE": "{{formattedDayCount}} день",
          "CARDINALITY_FEW": "{{formattedDayCount}} дні",
          "CARDINALITY_MANY": "{{formattedDayCount}} днів",
          "CARDINALITY_OTHER": "{{formattedDayCount}} дня"
        }
      }
    }
  }
}
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, decimal } from "lokalized";
import { readStringsFromDirectory } from "lokalized/node";

const locale = "uk";

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_ONE
const oneDayCount = 1;
const oneFormattedDayCount = numberFormat.format(oneDayCount);

cardinalityForNumber(oneDayCount, locale).name;   // => "CARDINALITY_ONE"
strings.get("Delivery.LeadTime", {
  dayCount: oneDayCount,
  formattedDayCount: oneFormattedDayCount,
});   // => "1 день"

// CARDINALITY_FEW
const fewDayCount = 2;
const fewFormattedDayCount = numberFormat.format(fewDayCount);

cardinalityForNumber(fewDayCount, locale).name;   // => "CARDINALITY_FEW"
strings.get("Delivery.LeadTime", {
  dayCount: fewDayCount,
  formattedDayCount: fewFormattedDayCount,
});   // => "2 дні"

// CARDINALITY_MANY
const manyDayCount = 0;
const manyFormattedDayCount = numberFormat.format(manyDayCount);

cardinalityForNumber(manyDayCount, locale).name;   // => "CARDINALITY_MANY"
strings.get("Delivery.LeadTime", {
  dayCount: manyDayCount,
  formattedDayCount: manyFormattedDayCount,
});   // => "0 днів"

// CARDINALITY_OTHER
const otherDayCount = decimal("0.0");
const otherFormattedDayCount = new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 })
  .format("0.0");

cardinalityForNumber(otherDayCount, locale).name;   // => "CARDINALITY_OTHER"
strings.get("Delivery.LeadTime", {
  dayCount: otherDayCount,
  formattedDayCount: otherFormattedDayCount,
});   // => "0,0 дня"

Java

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

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

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
Number oneDayCount = 1;
String oneFormattedDayCount = numberFormat.format(oneDayCount);

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

// Expected: 1 день
assertEquals(oneFormattedDayCount + " день", strings.get(
  "Delivery.LeadTime", Map.of("dayCount", oneDayCount, "formattedDayCount", oneFormattedDayCount)
));

// CARDINALITY_FEW
Number fewDayCount = 2;
String fewFormattedDayCount = numberFormat.format(fewDayCount);

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

// Expected: 2 дні
assertEquals(fewFormattedDayCount + " дні", strings.get(
  "Delivery.LeadTime", Map.of("dayCount", fewDayCount, "formattedDayCount", fewFormattedDayCount)
));

// CARDINALITY_MANY
Number manyDayCount = 0;
String manyFormattedDayCount = numberFormat.format(manyDayCount);

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

// Expected: 0 днів
assertEquals(manyFormattedDayCount + " днів", strings.get(
  "Delivery.LeadTime", Map.of("dayCount", manyDayCount, "formattedDayCount", manyFormattedDayCount)
));

// CARDINALITY_OTHER
Number otherDayCount = new java.math.BigDecimal("0.0");
NumberFormat otherDayCountFormat = NumberFormat.getNumberInstance(locale);
otherDayCountFormat.setMinimumFractionDigits(1);
otherDayCountFormat.setMaximumFractionDigits(1);
String otherFormattedDayCount = otherDayCountFormat.format(otherDayCount);

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

// Expected: 0,0 дня
assertEquals(otherFormattedDayCount + " дня", strings.get(
  "Delivery.LeadTime", Map.of("dayCount", otherDayCount, "formattedDayCount", otherFormattedDayCount)
));

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("uk")
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_ONE
let number1 = try NumericValue.forDecimal("1")
let category1 = try Cardinality.forNumber(number1, locale: locale.tag)
assert(category1 == .one)
// This fixed display value makes the translation assertion reproducible.
let display1 = "1"
let message1 = try strings.get("Delivery.LeadTime", placeholders: [
  "dayCount": .number(number1),
  "formattedDayCount": .text(display1),
])
assert(ExactString(message1) == ExactString("1 день"))

// CARDINALITY_FEW
let number2 = try NumericValue.forDecimal("2")
let category2 = try Cardinality.forNumber(number2, locale: locale.tag)
assert(category2 == .few)
// This fixed display value makes the translation assertion reproducible.
let display2 = "2"
let message2 = try strings.get("Delivery.LeadTime", placeholders: [
  "dayCount": .number(number2),
  "formattedDayCount": .text(display2),
])
assert(ExactString(message2) == ExactString("2 дні"))

// CARDINALITY_MANY
let number3 = try NumericValue.forDecimal("0")
let category3 = try Cardinality.forNumber(number3, locale: locale.tag)
assert(category3 == .many)
// This fixed display value makes the translation assertion reproducible.
let display3 = "0"
let message3 = try strings.get("Delivery.LeadTime", placeholders: [
  "dayCount": .number(number3),
  "formattedDayCount": .text(display3),
])
assert(ExactString(message3) == ExactString("0 днів"))

// CARDINALITY_OTHER
let number4 = try NumericValue.forDecimal("0.0")
let category4 = try Cardinality.forNumber(number4, locale: locale.tag)
assert(category4 == .other)
// This fixed display value makes the translation assertion reproducible.
let display4 = "0,0"
let message4 = try strings.get("Delivery.LeadTime", placeholders: [
  "dayCount": .number(number4),
  "formattedDayCount": .text(display4),
])
assert(ExactString(message4) == ExactString("0,0 дня"))

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_ONE - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • CARDINALITY_ONE - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_ONE - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_ONE - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_FEW - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • CARDINALITY_FEW - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_FEW - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_FEW - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_MANY - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • CARDINALITY_MANY - CARDINALITY_FEW

    Matches CARDINALITY_FEW

  • CARDINALITY_MANY - CARDINALITY_MANY

    Matches CARDINALITY_MANY

  • CARDINALITY_MANY - CARDINALITY_OTHER

    Matches CARDINALITY_OTHER

  • CARDINALITY_OTHER - CARDINALITY_ONE

    Matches CARDINALITY_ONE

  • 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
uk.json Localized Strings File (Ukrainian)
{
  "Delivery.Window": {
    "commentary": "Estimated delivery window, expressed as a range of days.",
    "translation": "{{window}}",
    "placeholders": {
      "window": {
        "range": {
          "start": "minDays",
          "end": "maxDays"
        },
        "translations": {
          "CARDINALITY_ONE": "{{formattedDayRange}} день",
          "CARDINALITY_FEW": "{{formattedDayRange}} дні",
          "CARDINALITY_MANY": "{{formattedDayRange}} днів",
          "CARDINALITY_OTHER": "{{formattedDayRange}} дня"
        }
      }
    }
  }
}
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 = "uk";

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 = 1;
const formattedMinDays = numberFormat.format(minDays);
const maxDays = 21;
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 });   // => "1-21 день"

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("uk");

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 = 1;
String formattedMinDays = numberFormat.format(minDays);
Number maxDays = 21;
String formattedMaxDays = numberFormat.format(maxDays);
String formattedDayRange = formattedMinDays + "-" + formattedMaxDays;

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

// Expected: 1-21 день
assertEquals(formattedDayRange + " день", 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("uk")
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("1")
let end = try NumericValue.forDecimal("21")
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("1-21"),
])
assert(ExactString(message1) == ExactString("1-21 день"))

Ordinalities

  • ORDINALITY_FEW

    Matches n % 10 = 3 and n % 100 != 13

    Example Integers: 3, 23, 33, 43, 53, 63, 73, 83, 103, 1003, …

  • ORDINALITY_OTHER

    Matches all other values

    Example Integers: 0, 2, 4, 16, 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 uk locale data.

Sample localized strings file
uk.json Localized Strings File (Ukrainian)
{
  "Ordinal.MinimalPair": {
    "commentary": "Demonstrates every CLDR ordinal category defined for this locale.",
    "translation": "{{ordinalExample}}",
    "placeholders": {
      "ordinalExample": {
        "value": "ordinalValue",
        "translations": {
          "ORDINALITY_FEW": "{{formattedOrdinalValue}}-я дивізія, {{formattedOrdinalValue}}-є коло",
          "ORDINALITY_OTHER": "{{formattedOrdinalValue}}-а дивізія, {{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 = "uk";

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_FEW
const fewOrdinalValue = 3;
const fewFormattedOrdinalValue = numberFormat.format(fewOrdinalValue);

ordinalityForNumber(fewOrdinalValue, locale).name;   // => "ORDINALITY_FEW"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: fewOrdinalValue,
  formattedOrdinalValue: fewFormattedOrdinalValue,
});   // => "3-я дивізія, 3-є коло"

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

ordinalityForNumber(otherOrdinalValue, locale).name;   // => "ORDINALITY_OTHER"
strings.get("Ordinal.MinimalPair", {
  ordinalValue: otherOrdinalValue,
  formattedOrdinalValue: otherFormattedOrdinalValue,
});   // => "2-а дивізія, 2-е коло"

Java

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

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

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_FEW
Number fewOrdinalValue = 3;
String fewFormattedOrdinalValue = numberFormat.format(fewOrdinalValue);

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

// Expected: 3-я дивізія, 3-є коло
assertEquals(
  fewFormattedOrdinalValue + "-я дивізія, " + fewFormattedOrdinalValue + "-є коло",
  strings.get(
    "Ordinal.MinimalPair",
    Map.of("ordinalValue", fewOrdinalValue, "formattedOrdinalValue", fewFormattedOrdinalValue)
  )
);

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

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

// Expected: 2-а дивізія, 2-е коло
assertEquals(
  otherFormattedOrdinalValue + "-а дивізія, " + 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("uk")
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("3")
let category1 = try Ordinality.forNumber(number1, locale: locale.tag)
assert(category1 == .few)
let message1 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number1),
  "formattedOrdinalValue": .text("3"),
])
assert(ExactString(message1) == ExactString("3-я дивізія, 3-є коло"))

let number2 = try NumericValue.forDecimal("2")
let category2 = try Ordinality.forNumber(number2, locale: locale.tag)
assert(category2 == .other)
let message2 = try strings.get("Ordinal.MinimalPair", placeholders: [
  "ordinalValue": .number(number2),
  "formattedOrdinalValue": .text("2"),
])
assert(ExactString(message2) == ExactString("2-а дивізія, 2-е коло"))

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