adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
13.12k stars 1.15k forks source link

Externalize localization strings #3794

Closed kentcdodds closed 1 year ago

kentcdodds commented 2 years ago

๐Ÿ™‹ Feature Request

I was exploring using useComboBox and noticed it says internationalization is handled. After exploring I realized that the localization strings for every language are literally included in the build for @react-aria/combobox. I was disappointed by this because it means that there are translations in there for languages I need to support as well as potentially missing other languages that I do want to support.

In addition, it means that we're sending the localization strings to clients that don't need them. That adds up.

I'd like to invert control on localization strings so I can import a build of react-aria packages that does not include the localization strings and instead I provide them myself.

๐Ÿค” Expected Behavior

In my mind it could be a part of the API contract of useComboBox:

useComboBox({
  ...props,
  localization: {
    'en-US': {
      "focusAnnouncement": (args, formatter)=>`${formatter.select({
              true: ()=>`Entered group ${args.groupTitle}, with ${formatter.plural(args.groupCount, {
                      one: ()=>`${formatter.number(args.groupCount)} option`
                      ,
                      other: ()=>`${formatter.number(args.groupCount)} options`
                  })}. `
              ,
              other: ``
          }, args.isGroupChange)}${args.optionText}${formatter.select({
              true: `, selected`,
              other: ``
          }, args.isSelected)}`
      ,
      "countAnnouncement": (args, formatter)=>`${formatter.plural(args.optionCount, {
              one: ()=>`${formatter.number(args.optionCount)} option`
              ,
              other: ()=>`${formatter.number(args.optionCount)} options`
          })} available.`
      ,
      "selectedAnnouncement": (args)=>`${args.optionText}, selected`
      ,
      "buttonLabel": `Show suggestions`,
      "listboxLabel": `Suggestions`
    }
  }
})

Of course, these could be imported from react-aria to make it easier for most folks:

import {useComboBox} from 'react-aria/unlocalized'
import {enUS} from '@react-aria/combobox/localization'

useComboBox({
  ...props,
  localization: {
    'en-US': enUS
  }
})

Also, it may be good to specify a fallback (in the event a translation isn't available:

import {useComboBox} from 'react-aria/unlocalized'
import {enUS} from '@react-aria/combobox/localization'
import {enES} from 'my-own/localization'

useComboBox({
  ...props,
  localizationFallback: 'en-US',
  localization: {
    'en-US': enUS,
    'es-ES': enES
  }
})

๐Ÿ˜ฏ Current Behavior

Right now all the localization strings are included and most are unused for all clients.

๐Ÿ’ Possible Solution

This would naturally be a major breaking change to implement on top of existing components. So I suggest a separate build for all packages that excludes the internationalization files (hence my imports are react-aria/unlocalized as an example). There would also need to be some kind of build to generate the localization files.

๐Ÿ”ฆ Context

I'm building an app that I do want localized, but I don't want my Spanish-speaking users to pay the cost of 33 other languages for every react-aria hook that comes with baked-in localization for all the other languages.

๐Ÿ’ป Examples

N/A

๐Ÿงข Your Company/Team

Not Adobe. I'm building course material for https://EpicWeb.dev and considering using React Aria in that material.

๐ŸŽ Tracking Ticket (optional)

N/A

devongovett commented 2 years ago

Definitely something we want to do. I wrote up something similar in https://github.com/adobe/react-spectrum/discussions/2999#discussioncomment-2697600 as well. The separate unlocalized build is an interesting idea to solve the breaking change problem.

I don't want my Spanish-speaking users to pay the cost of 33 other languages

Ideally, they'd only load Spanish, and not the whole set of languages that your app supports (even if that's less than 33). But not sure how to do that without incurring lots of extra HTTP requests for each component, or doing something fancy with a specific bundler/SSR setup. Listed some options in my comment linked above. Do you have any thoughts on how the correct language pack could be loaded for an individual user?

kentcdodds commented 2 years ago

doing something fancy with a specific bundler/SSR setup.

I think by inverting control you do have to force people to do some setup themselves. In every company I've been at that did localization we had to do something fancy with localization strings and everywhere had a different approach. I don't think a library can really solve this problem generically. But you definitely will want to have guides for suggestions on how to do this.

The SSR story is the one that applies to me and I'm yet to try localization with Remix, but my impression is that I will treat my localization strings as data and load it in my loader. Once I dive deeper into that I'll probably have a more useful example to share.

For the non-SSR case, I think people could generate the localization strings they want included and have those available at build-time.