Last active
February 24, 2023 19:27
-
-
Save OliverJAsh/da137c622a662d5e449f3a99c6cd1e79 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as A from 'fp-ts/Array'; | |
import { pipe } from 'fp-ts/function'; | |
import * as IO from 'fp-ts/IO'; | |
// Space: https://unicodeplus.com/U+0020 | |
const space = String.fromCharCode(32); | |
// Narrow No-Break Space: https://unicodeplus.com/U+202F | |
const narrowNoBreakSpace = String.fromCharCode(8239); | |
// Thin Space: https://unicodeplus.com/U+2009 | |
const thinSpace = String.fromCharCode(8201); | |
/** | |
* A patched alternative to `Intl.DateTimeFormat`, fixing React hydration for some usages of the | |
* `format` and `formatRange` methods. | |
* | |
* Specifically some platforms will render irregular spaces in place of regular ones. This is caused by | |
* an ICU mismatch between versions and across platforms. Consider that our server-side React is | |
* rendered on Linux, so a macOS client at time of writing won't be able to hydrate without this | |
* patch. | |
* | |
* @see https://github.com/unsplash/unsplash-web/pull/9535#issuecomment-1429513353 | |
* @see https://github.com/unicode-org/icu/pull/2103 | |
* @see https://github.com/unicode-org/cldr/pull/2001 | |
*/ | |
class PatchedDateTimeFormat extends Intl.DateTimeFormat { | |
format(...args: Parameters<Intl.DateTimeFormat['format']>): string { | |
return super.format(...args).replaceAll(narrowNoBreakSpace, space); | |
} | |
formatRange(...args: Parameters<Intl.DateTimeFormat['formatRange']>): string { | |
return super | |
.formatRange(...args) | |
.replaceAll(thinSpace, space) | |
.replaceAll(narrowNoBreakSpace, space); | |
} | |
formatRangeToParts( | |
...args: Parameters<Intl.DateTimeFormat['formatRangeToParts']> | |
): Array<Intl.DateTimeRangeFormatPart> { | |
return pipe( | |
super.formatRangeToParts(...args), | |
A.map( | |
(p): Intl.DateTimeRangeFormatPart => ({ | |
...p, | |
value: p.value.replaceAll(thinSpace, space).replaceAll(narrowNoBreakSpace, space), | |
}), | |
), | |
); | |
} | |
formatToParts( | |
...args: Parameters<Intl.DateTimeFormat['formatToParts']> | |
): Array<Intl.DateTimeFormatPart> { | |
return pipe( | |
super.formatToParts(...args), | |
A.map( | |
(p): Intl.DateTimeFormatPart => ({ | |
...p, | |
value: p.value.replaceAll(narrowNoBreakSpace, space), | |
}), | |
), | |
); | |
} | |
} | |
const originalDateTimeFormat = Intl.DateTimeFormat; | |
/** | |
* Patch `Intl.DateTimeFormat` globally, fixing React hydration. See `PatchedDateTimeFormat`. | |
*/ | |
export const patch: IO.IO<void> = () => { | |
Intl.DateTimeFormat = PatchedDateTimeFormat as typeof Intl.DateTimeFormat; | |
}; | |
export const unpatch: IO.IO<void> = () => { | |
Intl.DateTimeFormat = originalDateTimeFormat; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment