diff --git a/.changeset/sixty-zebras-battle.md b/.changeset/sixty-zebras-battle.md new file mode 100644 index 0000000000..d4cca90bdb --- /dev/null +++ b/.changeset/sixty-zebras-battle.md @@ -0,0 +1,7 @@ +--- +'@graphcommerce/magento-product-configurable': patch +'@graphcommerce/lingui-next': patch +'@graphcommerce/next-ui': patch +--- + +Don't render pseudo-locale in HTML lang attribute diff --git a/examples/magento-graphcms/pages/_document.tsx b/examples/magento-graphcms/pages/_document.tsx index 5bf98316e8..26a35dab3f 100644 --- a/examples/magento-graphcms/pages/_document.tsx +++ b/examples/magento-graphcms/pages/_document.tsx @@ -1,3 +1,4 @@ +import { normalizeLocale } from '@graphcommerce/lingui-next' import { withLingui } from '@graphcommerce/lingui-next/document/withLingui' import type { LinguiDocumentProps } from '@graphcommerce/lingui-next/document/withLingui' import { EmotionCacheProps, withEmotionCache } from '@graphcommerce/next-ui' @@ -6,7 +7,7 @@ import NextDocument, { Html, Head, Main, NextScript } from 'next/document' class Document extends NextDocument { render() { return ( - + {/* Inject MUI styles first to match with the prepend: true configuration. */} diff --git a/packages/lingui-next/index.ts b/packages/lingui-next/index.ts index c89af51a4a..26e9f049bb 100644 --- a/packages/lingui-next/index.ts +++ b/packages/lingui-next/index.ts @@ -1,2 +1,3 @@ export * from './components/LinguiProvider' export * from './types' +export * from './lib/normalizeLocale' diff --git a/packages/lingui-next/lib/normalizeLocale.ts b/packages/lingui-next/lib/normalizeLocale.ts new file mode 100644 index 0000000000..a70ef58901 --- /dev/null +++ b/packages/lingui-next/lib/normalizeLocale.ts @@ -0,0 +1,24 @@ +/** + * To support using multiple storefronts using the same language locale (which + * next.js does not support), we use an additional 'tag' in the locale code in + * which we specify a unique string (i.e. a Magento store code). + * + * This makes next.js happy, as it still follows the BCP47 spec. However, the + * Intl API and other places may not accept this as a valid locale. + * + * Use this method to get a 'normalized' locale that can safely be used in such + * places. + */ +export function normalizeLocale(locale: string | undefined) { + if (!locale) return locale + + // Specifically match the xx-yy-storecode format, so we don't accidently 'fix' + // valid locales such as he-Hebr-IL or zh-Hans-CN. This this isn't perfect and + // we should consider a more formalized way to use such pseudo-locales, which + // can be matched more precisely. + const matches = locale?.match(/([a-z]{2})-([a-z]{2})-([a-z]+)/i) + if (matches) { + return `${matches[1]}-${matches[2]}` + } + return locale +} diff --git a/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx b/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx index cf650552b6..4e93c897ed 100644 --- a/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx +++ b/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx @@ -1,3 +1,4 @@ +import { normalizeLocale } from '@graphcommerce/lingui-next' import { AddToCartItemSelector, useFormAddProductsToCart } from '@graphcommerce/magento-product' import { filterNonNullableKeys, ActionCardListProps } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' @@ -41,10 +42,10 @@ export function ConfigurableProductOptions(props: ConfigurableProductOptionsProp .length === 0 const allLabels = useMemo(() => { - // Remove optional dialect from locale, which Intl.NumberFormat does not support. - const strippedLocale = locale?.split('-', 2).join('-') - - const formatter = new Intl.ListFormat(strippedLocale, { style: 'long', type: 'conjunction' }) + const formatter = new Intl.ListFormat(normalizeLocale(locale), { + style: 'long', + type: 'conjunction', + }) return formatter.format(options.map((o) => o.label)) }, [locale, options]) diff --git a/packages/magento-product-configurable/package.json b/packages/magento-product-configurable/package.json index 5b24990e47..b265f3baee 100644 --- a/packages/magento-product-configurable/package.json +++ b/packages/magento-product-configurable/package.json @@ -17,6 +17,7 @@ "@graphcommerce/graphql": "^8.0.0-canary.78", "@graphcommerce/graphql-mesh": "^8.0.0-canary.78", "@graphcommerce/image": "^8.0.0-canary.78", + "@graphcommerce/lingui-next": "8.0.0-canary.78", "@graphcommerce/magento-cart": "^8.0.0-canary.78", "@graphcommerce/magento-cart-items": "^8.0.0-canary.78", "@graphcommerce/magento-category": "^8.0.0-canary.78", diff --git a/packages/next-ui/hooks/useDateTimeFormat.ts b/packages/next-ui/hooks/useDateTimeFormat.ts index 43f7dfb457..afb666fbe1 100644 --- a/packages/next-ui/hooks/useDateTimeFormat.ts +++ b/packages/next-ui/hooks/useDateTimeFormat.ts @@ -1,3 +1,4 @@ +import { normalizeLocale } from '@graphcommerce/lingui-next' import { useRouter } from 'next/router' import { useMemo } from 'react' @@ -6,12 +7,9 @@ export type DateTimeFormatProps = Intl.DateTimeFormatOptions export function useDateTimeFormat(props?: DateTimeFormatProps) { const { locale } = useRouter() - // Remove optional dialect from locale, which Intl.NumberFormat does not support. - const strippedLocale = locale?.split('-', 2).join('-') - const formatter = useMemo( - () => new Intl.DateTimeFormat(strippedLocale, props), - [strippedLocale, props], + () => new Intl.DateTimeFormat(normalizeLocale(locale), props), + [locale, props], ) return formatter } diff --git a/packages/next-ui/hooks/useNumberFormat.ts b/packages/next-ui/hooks/useNumberFormat.ts index 576ce84241..580ee546d6 100644 --- a/packages/next-ui/hooks/useNumberFormat.ts +++ b/packages/next-ui/hooks/useNumberFormat.ts @@ -1,3 +1,4 @@ +import { normalizeLocale } from '@graphcommerce/lingui-next' import { useRouter } from 'next/router' import { useMemo } from 'react' @@ -6,12 +7,9 @@ export type NumberFormatProps = Intl.NumberFormatOptions export function useNumberFormat(props?: NumberFormatProps) { const { locale } = useRouter() - // Remove optional dialect from locale, which Intl.NumberFormat does not support. - const strippedLocale = locale?.split('-', 2).join('-') - const formatter = useMemo( - () => new Intl.NumberFormat(strippedLocale, props), - [strippedLocale, props], + () => new Intl.NumberFormat(normalizeLocale(locale), props), + [locale, props], ) return formatter } diff --git a/packages/next-ui/package.json b/packages/next-ui/package.json index bf0fce221b..b145a1d274 100644 --- a/packages/next-ui/package.json +++ b/packages/next-ui/package.json @@ -31,6 +31,7 @@ "@graphcommerce/framer-scroller": "^8.0.0-canary.78", "@graphcommerce/framer-utils": "^8.0.0-canary.78", "@graphcommerce/image": "^8.0.0-canary.78", + "@graphcommerce/lingui-next": "^8.0.0-canary.78", "@graphcommerce/prettier-config-pwa": "^8.0.0-canary.78", "@graphcommerce/typescript-config-pwa": "^8.0.0-canary.78", "@lingui/core": "^4.2.1", diff --git a/yarn.lock b/yarn.lock index 7db8fe5c91..0e920e0232 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3967,6 +3967,7 @@ __metadata: "@graphcommerce/graphql": ^8.0.0-canary.78 "@graphcommerce/graphql-mesh": ^8.0.0-canary.78 "@graphcommerce/image": ^8.0.0-canary.78 + "@graphcommerce/lingui-next": 8.0.0-canary.78 "@graphcommerce/magento-cart": ^8.0.0-canary.78 "@graphcommerce/magento-cart-items": ^8.0.0-canary.78 "@graphcommerce/magento-category": ^8.0.0-canary.78 @@ -4300,6 +4301,7 @@ __metadata: "@graphcommerce/framer-scroller": ^8.0.0-canary.78 "@graphcommerce/framer-utils": ^8.0.0-canary.78 "@graphcommerce/image": ^8.0.0-canary.78 + "@graphcommerce/lingui-next": ^8.0.0-canary.78 "@graphcommerce/prettier-config-pwa": ^8.0.0-canary.78 "@graphcommerce/typescript-config-pwa": ^8.0.0-canary.78 "@lingui/core": ^4.2.1