Skip to content

Conversation

jbovenschen
Copy link
Contributor

Closes #5104

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

This feature is testable by either looking up different forms of pluralisation in the https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html#comparison table. This can be done by updating the formatOptions on a NumberField component to { style: 'unit', type: 'year' } for example.

// This list is derived from https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html#comparison and includes
// all unique numbers which we need to check in order to determine all the plural forms for a given locale.
// TODO: add a link to the used script
const pluralNumbers = [
Copy link
Contributor Author

@jbovenschen jbovenschen Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list of values I've extracted from the table on https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html#comparison. Using the following script:

function getRange(row) {
  let range = [];

  let th = row.firstElementChild;

  do {
    const { textContent } = th;

    let [start, end = start] = textContent.split('-');

    if (start === '') {
      range.push([]);
    } else {
      start = start.replace(/\.x$/, '.1');
      end = end.replace(/\.x$/, '.1');

      range.push([Number(start), Number(end)]);
    }
  } while ((th = th.nextElementSibling));

  return range;
}

function getLocales(cell) {
  let locales = [];

  let element = cell.firstElementChild;

  do {
    if (element.tagName === 'SPAN') {
      locales.push(element.title);
    }
  } while ((element = element.nextElementSibling));

  return locales;
}

function getPlurals(tr, categories, range) {
  const rules = Object.fromEntries(categories.map((key) => [key, []]));

  let td = tr.firstElementChild;

  let index = 1;

  do {
    const category = td.title;

    let columns = td.hasAttribute('colspan')
      ? Number(td.getAttribute('colspan'))
      : 1;

    do {
      rules[category].push(range[index]);

      index++;
    } while (columns-- > 1);
  } while ((td = td.nextElementSibling));

  return rules;
}

function extractTable(integerTable, fractionTable) {
  function extract(table) {
    const tbody = table.querySelector('tbody');

    let tr = tbody.firstElementChild;

    let current;
    let results = {};

    do {
      if (tr.firstElementChild.tagName === 'TH') {
        if (current) {
          for (const language of current.languages) {
            if (!results[language]) {
              results[language] = Object.fromEntries(
                Object.keys(current.rules).map((key) => [key, []])
              );
            }

            for (let rule in current.rules) {
              results[language][rule] = [].concat(
                results[language][rule],
                current.rules[rule]
              );
            }
          }
        }

        current = {
          range: [],
          languages: [],
          rules: {},
        };

        current.range = getRange(tr);
      } else if (
        tr.children[1] instanceof HTMLTableCellElement &&
        tr.children[1].classList.contains('l')
      ) {
        current.languages = getLocales(tr.children[1]);
      } else {
        const [lang] = current['languages'];

        const { pluralCategories } = new Intl.PluralRules(
          lang
        ).resolvedOptions();

        current.rules = getPlurals(tr, pluralCategories, current.range);
      }
    } while ((tr = tr.nextElementSibling));

    return results;
  }

  const integer = extract(integerTable);
  const fraction = extract(fractionTable);

  let values = new Set();

  for (let language in integer) {
    for (let rule in integer[language]) {
      if (integer[language][rule].length > 1) {
        values.add(integer[language][rule][0][0]);
      }
    }
  }

  for (let language in fraction) {
    for (let rule in fraction[language]) {
      if (fraction[language][rule].length > 1) {
        values.add(fraction[language][rule][0][0]);
      }
    }
  }

  return Array.from(values);
}

const [integerTable, fractionTable] = document.querySelectorAll('.pluralComp');

let values = extractTable(integerTable, fractionTable);

The code itself is not written optimal but it works to get a list of values with which we would get all plural forms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wonderful, thank you for sharing this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the script to our repo
#5141

// This list is derived from https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html#comparison and includes
// all unique numbers which we need to check in order to determine all the plural forms for a given locale.
// TODO: add a link to the used script
const pluralNumbers = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wonderful, thank you for sharing this.

@snowystinger snowystinger mentioned this pull request Sep 27, 2023
5 tasks
@snowystinger snowystinger merged commit d40e4fa into adobe:main Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue with pluralisation in the NumberField

3 participants