Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fall back to 'other' if a plural form is missing #2921

Merged
merged 6 commits into from
Oct 20, 2022
Merged

Conversation

36degrees
Copy link
Contributor

If a service currently has a character count inside an element with a non-English lang tag and tried to use the current implementation, the Character Count may throw an error and not update the count message when the user has a certain number of characters remaining.

This is because we are only providing default translations for the one and other plural forms (the only forms required in English).

But if the character count was in a cy-GB locale for example it would expect plural forms for two, few and many, and when the user got down to 6-2 characters remaining an error would be thrown and the count message would not be updated.

In order to preserve the existing behaviour, falling back to other is a reasonable thing to do, even if it may lead to incorrect plural forms being used in some cases.

Closes #2918.

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 14, 2022 09:28 Inactive
@36degrees
Copy link
Contributor Author

36degrees commented Oct 14, 2022

I am not 100% sure about the warning – I think there are cases where users could omit plural forms and the translations would be fine.

Just because a language might use a different plural form, doesn't mean it will.

To give a slightly silly example, this would log a warning, but the localisation is perfectly correct:

const i18n = new I18n({
    'test.other': 'Ollie has %{count} sheep'
}

i18n.t('test', { count: 1 }) // Ollie has 1 sheep
i18n.t('test', { count: 2 }) // Ollie has 2 sheep

To silence the warning, they'd need to pass test.one even though it works exactly the same as other. Should we be trying to enforce passing all plural forms in code when they might not actually be needed?

// to the console
} else if (lookupKey + '.other' in this.translations) {
if (console && 'warn' in console) {
console.warn('i18n: Missing plural form ".' + pluralSuffix + '" for "' +
Copy link
Member

Choose a reason for hiding this comment

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

The message formatting seems a bit inconsistent as it refers to the missing key as ".many" but then says it's falling back to the `other` key. I'd expect the missing key to probably also be written as `many`.

Otherwise this all looks good to me. Seems to do the job.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! 👀

@36degrees 36degrees force-pushed the i18n-fall-back-other branch from db17289 to 2399bd5 Compare October 14, 2022 09:49
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 14, 2022 09:49 Inactive
@querkmachine
Copy link
Member

Should we be trying to enforce passing all plural forms in code when they might not actually be needed?

I'm gonna say yes, if just for consistency and error checking's sakes. In that example, it's more coincidental that the one and other forms of 'sheep' are the same than by the design of the language.

A reverse argument might be that we shouldn't require other in all situations because Chinese, Malaysian, Indonesian, etc. don't have plural forms at all and all nouns are singular. Russian is similarly weird, with the other form only used for decimal numbers, with all whole numbers using one of the other plural forms.

At least by pushing a warning out for a missing form, we can assist developers (who may not be speakers of the language at hand) in knowing if a required string is likely to be missing or malformed.

Copy link
Contributor

@colinrotherham colinrotherham left a comment

Choose a reason for hiding this comment

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

Cheers @36degrees looks good

Sheep example aside (deer, fish, moose… Emoji) it's all working. Perhaps good to consider the page weight increase having to duplicate lots of text for these cases?

Added some nice-to-have (but not blocking) comments

// If the required `other` plural form is missing, all we can do is error
} else {
throw new Error(
'i18n: Plural form ".other" is required for "' + this.locale + '" locale'
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth trying to share these messages between the new Error() and console.warn()?

var pluralSuffixError = 'i18n: Plural form ".' + pluralSuffix + '" is required for "' + this.locale + '" locale'
var pluralSuffixWarn = pluralSuffixError + '. Falling back to `other`.'
console.warn(pluralSuffixWarn)
throw new Error(pluralSuffixError)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've opted to leave this as is for now as it didn't feel quite right to describe the missing plural forms as required when there's something we can fall back to.

(I think this ties in slightly to Beep's comment about future behaviour and what we want to do.)

expect(i18n.t('test', { count: 1 })).toBe('testing testing')
})

it('logs a console warning when falling back to `other`', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm seeing .many, .few and .two logged (not just .one)

Shall we do one of those fancy it.each([]) and make sure each plural form has a test?

Looks good

Copy link
Contributor

Choose a reason for hiding this comment

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

This came up on Slack, so bringing over the points here

  1. Do we suppress .two, .few, .many warnings, for all locales?
  2. Do we suppress .two, .few, .many warnings, for English only?
  3. Treating all locales equally, is it correct to need duplicate strings by design?
var TRANSLATIONS_DEFAULT = {
  // Characters
  charactersUnderLimit: {
    one: 'You have %{count} character remaining',
    two: 'You have %{count} characters remaining',
    few: 'You have %{count} characters remaining',
    many: 'You have %{count} characters remaining',
    other: 'You have %{count} characters remaining'
  },
  charactersAtLimit: 'You have 0 characters remaining',
  charactersOverLimit: {
    one: 'You have %{count} character too many',
    two: 'You have %{count} characters too many',
    few: 'You have %{count} characters too many',
    many: 'You have %{count} characters too many',
    other: 'You have %{count} characters too many'
  },
  // Words
  wordsUnderLimit: {
    one: 'You have %{count} word remaining',
    two: 'You have %{count} words remaining',
    few: 'You have %{count} words remaining',
    many: 'You have %{count} words remaining',
    other: 'You have %{count} words remaining'
  },
  wordsAtLimit: 'You have 0 words remaining',
  wordsOverLimit: {
    one: 'You have %{count} word too many',
    two: 'You have %{count} words too many',
    few: 'You have %{count} words too many',
    many: 'You have %{count} words too many',
    other: 'You have %{count} words too many'
  }
}

To silence the warning, they'd need to pass test.one even though it works exactly the same as other. Should we be trying to enforce passing all plural forms in code when they might not actually be needed?

👆 Above would be the Character Count defaults if we needed all strings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Different plural forms are required depending on which locale the Character Count is in.

When in the default 'en' locale, only 'one' and 'other' are required.

So, I think the answers are:

  1. We should warn when a plural form is missing that we try to use based on the locale, so 'it depends on the locale'
  2. There shouldn't be any cases where we warn for a character count in the en locale, because we have all of the plural forms we need to localise in English ('one' and 'other')
  3. I think that's what I'm asking with my question about the sheep. But I don't think we'd add plural forms for anything other than 'one' and 'other' to the defaults as the defaults are only designed to be used in an en locale – we'd expect if the locale is something other than en that we'd have a different set of translations passed with the correct plural forms for the locale. This PR is trying to address the edge case where that doesn't happen.

Copy link
Contributor

Choose a reason for hiding this comment

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

@36degrees Perfect, thanks for the answers

Shall we do one of those fancy it.each([]) and make sure each plural form has a test?

Do you want to add tests to confirm?

  1. English: Warnings do not get logged for missing .two, .few, .many
  2. Others: Warnings do get logged for missing .two, .few, .many

We've got this one already, but won't harm to document this decision about the others in test form

expect(consoleWarn).toHaveBeenCalledWith(
  'i18n: Missing plural form ".one" for "en" locale. Falling back to ".other".'
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm wary of testing for specific locales as it feels like we're starting to test the implementation of Intl.PluralRules.select rather than our code.

The reason that we shouldn't output any warnings for the en locale is because of how the class interacts with the config for the character count – there is nothing happening in the I18n class itself that means we treat the en locale any differently to any other locale.

I can add comments to the tests to try and make it clearer how the config we're passing in tests specific scenarios, if that'd help?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, makes sense

I like to think if a less experienced developer touches these lines in future, and they break something, the tests should be there to explain exactly why the change broke something—gives them a safety net.

Also, if we fix a Welsh bug, add a Welsh test case 😆

If it feels better in the Puppeteer tests, further away from I18n, can do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a failing test to the Character Count component in c757025.

@36degrees 36degrees force-pushed the i18n-fall-back-other branch from 2399bd5 to 8aec4df Compare October 14, 2022 16:07
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 14, 2022 16:07 Inactive
Base automatically changed from character-count-i18n-macro to main October 17, 2022 09:01
@36degrees 36degrees force-pushed the i18n-fall-back-other branch from 8aec4df to af7856d Compare October 18, 2022 11:01
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 18, 2022 11:01 Inactive
@querkmachine
Copy link
Member

As a stray thought (sorry!), in my mind, the functionality to fall back to other is only present to avoid a breaking change in existing services where they upgrade Frontend, and have a situation where the page is set in one lang but the component has not been translated yet.

However, falling back to other is a 'best guess' assumption and is not reliably true for all languages, nor is this something we actually want service developers to use or rely on as an intended feature. (Ideally, all plural forms needed should be explicitly set.)

Would it be a good idea to have a follow up card to remove the fallback in a future breaking release?

@36degrees 36degrees force-pushed the i18n-fall-back-other branch from af7856d to be4023c Compare October 19, 2022 09:15
@36degrees 36degrees changed the base branch from main to move-custom-fallback-text-test October 19, 2022 09:16
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 19, 2022 09:16 Inactive
@36degrees 36degrees force-pushed the move-custom-fallback-text-test branch from b1c4243 to cef117d Compare October 19, 2022 09:40
Base automatically changed from move-custom-fallback-text-test to main October 19, 2022 09:50
@36degrees 36degrees force-pushed the i18n-fall-back-other branch from be4023c to 00f4566 Compare October 19, 2022 12:30
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 19, 2022 12:30 Inactive
@36degrees 36degrees force-pushed the i18n-fall-back-other branch from 00f4566 to 7005e6c Compare October 19, 2022 12:45
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 19, 2022 12:46 Inactive
If a service currently has a character count inside an element with a non-English `lang` tag and tried to use the current implementation, the Character Count may throw an error and not update the count message when the user has a certain number of characters remaining.

This is because we are only providing default translations for the `one` and `other` plural forms (the only forms required in English).

But if the character count was in a `cy-GB` locale for example it would expect plural forms for `two`, `few` and `many`, and when the user got down to 6-2 characters remaining an error would be thrown and the count message would not be updated.

In order to preserve the existing behaviour, falling back to `other` is a reasonable thing to do, even if it may lead to incorrect plural forms being used in some cases.
@36degrees 36degrees force-pushed the i18n-fall-back-other branch from 7005e6c to 99f72ae Compare October 20, 2022 08:20
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2921 October 20, 2022 08:21 Inactive
@36degrees 36degrees merged commit 1a760cd into main Oct 20, 2022
@36degrees 36degrees deleted the i18n-fall-back-other branch October 20, 2022 08:25
colinrotherham added a commit that referenced this pull request Nov 9, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 9, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 9, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
colinrotherham added a commit that referenced this pull request Nov 10, 2022
See: #2921

Whilst `other` is required in English and is used as a fallback, it won’t be in all locales
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.

Character Count component throws error with missing plural text
4 participants