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

feat: Type-safe global formats #1346

Merged
merged 25 commits into from
Sep 24, 2024

Conversation

dBianchii
Copy link
Contributor

@dBianchii dBianchii commented Sep 18, 2024

This PR adds a new configuration called IntlFormats, where users can link their global formatting to this interface, and allow for global formats typesafety to work across their app.

Closes #1112

Copy link

vercel bot commented Sep 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
next-intl-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 24, 2024 10:39am
next-intl-example-app-router ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 24, 2024 10:39am
next-intl-example-app-router-without-i18n-routing ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 24, 2024 10:39am

Copy link

vercel bot commented Sep 18, 2024

@dBianchii is attempting to deploy a commit to the next-intl Team on Vercel.

A member of the Team first needs to authorize it.

@dBianchii
Copy link
Contributor Author

dBianchii commented Sep 18, 2024

Hi @ amann/maintainers

You said in here that the package supports full, long, medium and short. I believe that this only applies for dates and times within messages, yes? In that comment, did you suggest to make those the defaults as well for the formatter?

You also said:

For number formatting there are defaults for currency and percent.

I haven't found this logic in the code, and also in my tests when I do format.number(5, "percent") or format.number(5, "currency"), nothing happens

Could you explain a bit more of what you meant?

EDIT: Looks like that the percent and currency are available for formatting within messages as well! Got it. In that case, I just need to understand how you think that these should relate to the formatter api.

Copy link
Owner

@amannn amannn left a comment

Choose a reason for hiding this comment

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

Damn you're quick! 🔥🔥

I somehow left this feature on the backlog for a long time, but you made it happen just over night! 😄 Super cool, thanks a lot!

I've left a few comments inline, let me know what you think!

Btw. regarding the defaults across usage in messages / via format.*: You're absolutely right, I've opened #1347 as a separate issue to discuss this. This is definitely out of scope for this PR and type-safety is only relevant for format.* calls, therefore we should assume no defaults there.

examples/example-app-router/src/i18n/request.ts Outdated Show resolved Hide resolved
packages/use-intl/src/core/createFormatter.tsx Outdated Show resolved Hide resolved
packages/use-intl/types/index.d.ts Outdated Show resolved Hide resolved
examples/example-app-router/src/i18n/request.ts Outdated Show resolved Hide resolved
examples/example-app-router/src/i18n/request.ts Outdated Show resolved Hide resolved
packages/use-intl/src/core/createFormatter.tsx Outdated Show resolved Hide resolved
@dBianchii
Copy link
Contributor Author

There is one problem with this right now:

I see that the global format api has been designed to be able to handle global formats for client and server differently/separately. (We must provide formats to both getRequestConfig and NextIntlClientProvider component. Each can take in different global formats. ) I don't really understand why it is this way, because in my opinion it would be better if we could just use the same global formats everywhere (client and server). The way I set this up makes it impossible to separate both types, and the user can only provide one type as IntlFormats.

How can we resolve this problem?

@amannn
Copy link
Owner

amannn commented Sep 19, 2024

it would be better if we could just use the same global formats everywhere (client and server)

Yep, I agree!

The discussion is related to the one about messages: Messages are defined globally, but it's the user's job to provide the relevant messages where relevant. This is to allow the user to optionally pass only certain messages to the client side to optimize bundle size, which is also the reason why the messages need to be passed explicitly to NextIntlClientProvider.

The current state of the library has the same mechanism for formats: In order to potentially reduce bundle size, formats aren't automatically inherited and need to be passed explicitly to NextIntlClientProvider. I've changed my mind in the meantime about this though: Out-of-the-box formats should be inherited automatically, and therefore the user doesn't have to worry. You could still opt-out with formats={null}. This change is already implemented on the v4 branch (see #1191), but the release is still a bit further down the road.

That being said, I think it's fine to model IntlFormats the same as the global IntlMessages, especially with the change in v4 coming up.

Hope that sounds reasonable to you!

@dBianchii
Copy link
Contributor Author

That's awesome to hear. Makes total sense to me. I'll continue with this decision in mind. Thanks!

@dBianchii
Copy link
Contributor Author

I was thinking of moving this section to the bottom of the page, so it talks about both cases. But for now I just left it like that because I think that moving it all the way down would require creating another heading which I don't want to do

- Configuration page: Smaller callout
- TypeScript page: Less redundancy, shorter text, shared troubleshooting section, consistent ordering of sample/instructions in sections
- Avoid `Partial<Format>`
Copy link
Owner

@amannn amannn left a comment

Choose a reason for hiding this comment

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

I had another final look and rephrased a few minor things along the way. I agree that the troubleshooting section can now be shared for both types.

I've also adapted the Formats type in #1367 for easier consumption.

All good from my side now, waiting for CI to turn green! 🚀

@amannn amannn changed the title feat: Typesafe Global Formats feat: Type-safe global formats Sep 24, 2024
@amannn
Copy link
Owner

amannn commented Sep 24, 2024

I did another final test in example-app-router-playground and found that interestingly, while type checking was strict and errors were reported where expected, auto-complete of TypeScript was broken.

I've experimented with a few options with this test suite:

/* eslint-disable @typescript-eslint/no-unused-vars */

const formats = {
  dateTime: {
    medium: {
      dateStyle: 'medium',
      timeStyle: 'short',
      hour12: false
    }
  }
};

type Formats = typeof formats;
interface IntlFormats extends Formats {}

type T1 = keyof IntlFormats['dateTime'] extends string
  ? keyof IntlFormats['dateTime'] | Intl.DateTimeFormatOptions
  : string | Intl.DateTimeFormatOptions;
type T2 = keyof IntlFormats['dateTime'] | Intl.DateTimeFormatOptions;
type T3 =
  | Extract<keyof IntlFormats['dateTime'], string>
  | Intl.DateTimeFormatOptions;

// Type checking works, but no auto-completion
const t1: T1 = 'medium';

// Auto-completion, no string default
const t2: T2 = 'medium';

// Auto-completion and string default ✅
const t3: T3 = 'medium';

ChatGPT gave me the tip of using Extract, this seems to cover all cases now: Auto-complete, errors where expected and a string default 💯. I've adapted this accordingly in cca5134.

@amannn amannn merged commit b7aa14e into amannn:main Sep 24, 2024
6 checks passed
@dBianchii dBianchii deleted the feat/typesafe-global-formats branch September 24, 2024 11:00
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.

Strict typing of global formats
2 participants