diff --git a/.changeset/slow-kiwis-exercise.md b/.changeset/slow-kiwis-exercise.md new file mode 100644 index 0000000000..ba6547aa46 --- /dev/null +++ b/.changeset/slow-kiwis-exercise.md @@ -0,0 +1,10 @@ +--- +'@graphcommerce/magento-product-configurable': patch +'@graphcommerce/magento-product': patch +'@graphcommerce/next-config': patch +'@graphcommerce/lingui-next': patch +'@graphcommerce/next-ui': patch +'@graphcommerce/docs': patch +--- + +Allow Lingui to use linguiLocale with country identifiers like `en-us`, it would always load `en` in this case. Introced a new `useLocale` hook to use the correct locale string to use in Intl methods. diff --git a/docs/framework/config.md b/docs/framework/config.md index 00fe54ee39..ee42249707 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -66,7 +66,7 @@ Examples: You can export configuration by running `yarn graphcommerce export-config` -## Extending the configuration in your project +## Extending the configuration in your project Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. @@ -344,7 +344,9 @@ All storefront configuration for the project #### locale: string (required) -Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers +Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work. + +This value can be used as a sub-path identifier only, make sure linguiLocale is configured for each URL. #### magentoStoreCode: string (required) @@ -400,7 +402,9 @@ Add a gcms-locales header to make sure queries return in a certain language, can #### linguiLocale: string -Specify a custom locale for to load translations. +Specify a custom locale for to load translations. Must be lowercase valid locale. + +This value is also used for the Intl. ### MagentoConfigurableVariantValues diff --git a/examples/magento-graphcms/lingui.config.js b/examples/magento-graphcms/lingui.config.js index 2f4d72db5b..6451efa7e2 100644 --- a/examples/magento-graphcms/lingui.config.js +++ b/examples/magento-graphcms/lingui.config.js @@ -5,6 +5,11 @@ const linguiNextConfig = require('@graphcommerce/lingui-next/config') const { loadConfig } = require('@graphcommerce/next-config') require('dotenv').config() -const locales = loadConfig(process.cwd()).storefront.map(({ locale }) => locale) +const locales = loadConfig(process.cwd()).storefront.map(({ locale, linguiLocale }) => { + if (linguiLocale) return linguiLocale + const matches = locale?.match(/([a-z]{2})-([a-z]{2})-([a-z]+)/i) + if (matches) return `${matches[1]}-${matches[2]}` + return locale +}) module.exports = linguiNextConfig({ locales }) diff --git a/packages/lingui-next/Config.graphqls b/packages/lingui-next/Config.graphqls index af99e047ea..a235a0a8d7 100644 --- a/packages/lingui-next/Config.graphqls +++ b/packages/lingui-next/Config.graphqls @@ -1,6 +1,8 @@ extend input GraphCommerceStorefrontConfig { """ - Specify a custom locale for to load translations. + Specify a custom locale for to load translations. Must be lowercase valid locale. + + This value is also used for the Intl. """ linguiLocale: String } diff --git a/packages/lingui-next/components/LinguiProvider.tsx b/packages/lingui-next/components/LinguiProvider.tsx index dc78ced54b..516ce71ee9 100644 --- a/packages/lingui-next/components/LinguiProvider.tsx +++ b/packages/lingui-next/components/LinguiProvider.tsx @@ -1,23 +1,29 @@ -import { storefrontConfig, storefrontConfigDefault } from '@graphcommerce/next-ui' +import { useLocale } from '@graphcommerce/next-ui' import { i18n, Messages } from '@lingui/core' import { I18nProvider, I18nProviderProps } from '@lingui/react' import React, { useMemo } from 'react' import { MessageLoader, SyncMessageLoader } from '../types' +import { normalizeLocale } from '../lib/normalizeLocale' export type LinguiProviderProps = Omit & { children: React.ReactNode loader: MessageLoader ssrLoader: SyncMessageLoader - locale: string + /** + * @deprecated not necessary anumore + */ + locale?: string } -export const localeConfig = (locale: string = storefrontConfigDefault().locale) => - storefrontConfig(locale)?.linguiLocale ?? locale?.split('-')[0] +/** + * @deprecated use normalizeLocale + */ +export const localeConfig = normalizeLocale export function LinguiProvider(props: LinguiProviderProps) { const { loader, ssrLoader, locale, ...i18nProviderProps } = props - const localeOnly = localeConfig(locale) + const localeOnly = useLocale() useMemo(() => { const data = globalThis.document?.getElementById('lingui') diff --git a/packages/lingui-next/config.js b/packages/lingui-next/config.js index 71cda98b7f..c1a6419686 100644 --- a/packages/lingui-next/config.js +++ b/packages/lingui-next/config.js @@ -15,9 +15,7 @@ function linguiNextConfig(config) { const { locales, ...otherConfig } = config return { orderBy: 'messageId', - locales: isMonorepo() - ? ['en', 'nl', 'fr', 'de', 'es', 'it'] - : config.locales.map((l) => l?.split('-')[0]), + locales: isMonorepo() ? ['en', 'nl', 'fr', 'de', 'es', 'it'] : config.locales, // formatOptions: { lineNumbers: false, origins: false, explicitIdAsDefault: true }, format: formatter({ explicitIdAsDefault: true, lineNumbers: false, origins: false }), catalogs: [ diff --git a/packages/lingui-next/document/withLingui.tsx b/packages/lingui-next/document/withLingui.tsx index 4d41df2555..0066378689 100644 --- a/packages/lingui-next/document/withLingui.tsx +++ b/packages/lingui-next/document/withLingui.tsx @@ -4,6 +4,7 @@ import { DocumentContext, DocumentInitialProps } from 'next/document' // eslint-disable-next-line @next/next/no-document-import-in-page import type NextDocument from 'next/document' import React from 'react' +import { normalizeLocale } from '../lib/normalizeLocale' import { MessageLoader } from '../types' export type LinguiDocumentProps = { linguiScriptTag: React.ReactNode } @@ -16,7 +17,7 @@ export function withLingui( static async getInitialProps(ctx: DocumentContext) { const initial = await Document.getInitialProps(ctx) - const locale = ctx.locale?.split('-')?.[0] + const locale = normalizeLocale(ctx.locale) if (!locale) return initial try { diff --git a/packages/lingui-next/lib/normalizeLocale.ts b/packages/lingui-next/lib/normalizeLocale.ts index a70ef58901..0b8b9d2175 100644 --- a/packages/lingui-next/lib/normalizeLocale.ts +++ b/packages/lingui-next/lib/normalizeLocale.ts @@ -1,3 +1,5 @@ +import { storefrontConfig, storefrontConfigDefault } from '@graphcommerce/next-ui' + /** * 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 @@ -9,16 +11,16 @@ * 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 +export function normalizeLocale(locale: string | undefined = storefrontConfigDefault().locale) { + const linguiLocale = storefrontConfig(locale)?.linguiLocale + if (linguiLocale) return linguiLocale // 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]}` - } + 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 4e93c897ed..dd71e71963 100644 --- a/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx +++ b/packages/magento-product-configurable/components/ConfigurableProductOptions/ConfigurableProductOptions.tsx @@ -1,6 +1,11 @@ import { normalizeLocale } from '@graphcommerce/lingui-next' import { AddToCartItemSelector, useFormAddProductsToCart } from '@graphcommerce/magento-product' -import { filterNonNullableKeys, ActionCardListProps } from '@graphcommerce/next-ui' +import { + filterNonNullableKeys, + ActionCardListProps, + useStorefrontConfig, + useLocale, +} from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' import { Box, SxProps, Theme } from '@mui/material' import { useRouter } from 'next/router' @@ -27,7 +32,6 @@ export function ConfigurableProductOptions(props: ConfigurableProductOptionsProp ...other } = props const { setError, clearErrors } = useFormAddProductsToCart() - const { locale } = useRouter() const options = filterNonNullableKeys(product.configurable_options, [ 'attribute_code', @@ -41,8 +45,9 @@ export function ConfigurableProductOptions(props: ConfigurableProductOptionsProp (configured?.configurable_product_options_selection?.options_available_for_selection ?? []) .length === 0 + const locale = useLocale() const allLabels = useMemo(() => { - const formatter = new Intl.ListFormat(normalizeLocale(locale), { + const formatter = new Intl.ListFormat(locale, { style: 'long', type: 'conjunction', }) diff --git a/packages/magento-product/components/AddProductsToCart/AddProductsToCartSnackbar.tsx b/packages/magento-product/components/AddProductsToCart/AddProductsToCartSnackbar.tsx index 26941d72fe..91007148f3 100644 --- a/packages/magento-product/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +++ b/packages/magento-product/components/AddProductsToCart/AddProductsToCartSnackbar.tsx @@ -9,9 +9,9 @@ import { MessageSnackbar, MessageSnackbarProps, nonNullable, + useLocale, } from '@graphcommerce/next-ui' import { Trans } from '@lingui/react' -import { useRouter } from 'next/router' import { useMemo } from 'react' import { findAddedItems } from './findAddedItems' import { toUserErrors } from './toUserErrors' @@ -26,9 +26,8 @@ export function AddProductsToCartSnackbar(props: AddProductsToCartSnackbarProps) const { errorSnackbar, successSnackbar } = props const { error, data, redirect, control, submittedVariables } = useFormAddProductsToCart() const formState = useFormState({ control }) - const { locale } = useRouter() - const formatter = new Intl.ListFormat(locale, { style: 'long', type: 'conjunction' }) + const formatter = new Intl.ListFormat(useLocale(), { style: 'long', type: 'conjunction' }) const userErrors = toUserErrors(data) diff --git a/packages/next-ui/TimeAgo/TimeAgo.tsx b/packages/next-ui/TimeAgo/TimeAgo.tsx index cba70e4938..be5d733fa9 100644 --- a/packages/next-ui/TimeAgo/TimeAgo.tsx +++ b/packages/next-ui/TimeAgo/TimeAgo.tsx @@ -1,17 +1,23 @@ +import { useLocale } from '../hooks/useLocale' + export type TimeAgoProps = { date: Date + /** + * @deprecated No longer used + */ locale?: string } export function TimeAgo(props: TimeAgoProps) { - const { date, locale = 'en' } = props + const { date } = props const msPerMinute = 60 * 1000 const msPerHour = msPerMinute * 60 const msPerDay = msPerHour * 24 const timestamp = date.getTime() const elapsed = Date.now() - timestamp - const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }) + + const rtf = new Intl.RelativeTimeFormat(useLocale(), { numeric: 'auto' }) if (elapsed < msPerMinute) { return {rtf.format(-Math.floor(elapsed / 1000), 'seconds')} diff --git a/packages/next-ui/hooks/index.ts b/packages/next-ui/hooks/index.ts index dbd1a3692a..303930da98 100644 --- a/packages/next-ui/hooks/index.ts +++ b/packages/next-ui/hooks/index.ts @@ -5,3 +5,4 @@ export * from './useNumberFormat' export * from './useMemoObject' export * from './useStorefrontConfig' export * from './useUrlQuery' +export * from './useLocale' diff --git a/packages/next-ui/hooks/useDateTimeFormat.ts b/packages/next-ui/hooks/useDateTimeFormat.ts index afb666fbe1..91c26c807d 100644 --- a/packages/next-ui/hooks/useDateTimeFormat.ts +++ b/packages/next-ui/hooks/useDateTimeFormat.ts @@ -1,15 +1,13 @@ import { normalizeLocale } from '@graphcommerce/lingui-next' -import { useRouter } from 'next/router' import { useMemo } from 'react' +import { useStorefrontConfig } from './useStorefrontConfig' +import { useLocale } from '@graphcommerce/next-ui' export type DateTimeFormatProps = Intl.DateTimeFormatOptions export function useDateTimeFormat(props?: DateTimeFormatProps) { - const { locale } = useRouter() + const locale = useLocale() - const formatter = useMemo( - () => new Intl.DateTimeFormat(normalizeLocale(locale), props), - [locale, props], - ) + const formatter = useMemo(() => new Intl.DateTimeFormat(locale, props), [locale, props]) return formatter } diff --git a/packages/next-ui/hooks/useLocale.ts b/packages/next-ui/hooks/useLocale.ts new file mode 100644 index 0000000000..5f7744a980 --- /dev/null +++ b/packages/next-ui/hooks/useLocale.ts @@ -0,0 +1,7 @@ +import { normalizeLocale } from '@graphcommerce/lingui-next' +import { useStorefrontConfig } from './useStorefrontConfig' + +export function useLocale() { + const { locale } = useStorefrontConfig() + return normalizeLocale(locale) +} diff --git a/packages/next-ui/hooks/useNumberFormat.ts b/packages/next-ui/hooks/useNumberFormat.ts index 580ee546d6..536620882e 100644 --- a/packages/next-ui/hooks/useNumberFormat.ts +++ b/packages/next-ui/hooks/useNumberFormat.ts @@ -1,15 +1,10 @@ -import { normalizeLocale } from '@graphcommerce/lingui-next' -import { useRouter } from 'next/router' import { useMemo } from 'react' +import { useLocale } from './useLocale' export type NumberFormatProps = Intl.NumberFormatOptions export function useNumberFormat(props?: NumberFormatProps) { - const { locale } = useRouter() - - const formatter = useMemo( - () => new Intl.NumberFormat(normalizeLocale(locale), props), - [locale, props], - ) + const locale = useLocale() + const formatter = useMemo(() => new Intl.NumberFormat(locale, props), [locale, props]) return formatter } diff --git a/packagesDev/next-config/Config.graphqls b/packagesDev/next-config/Config.graphqls index 05f9839015..f2aa2ca33b 100644 --- a/packagesDev/next-config/Config.graphqls +++ b/packagesDev/next-config/Config.graphqls @@ -58,7 +58,7 @@ Examples: You can export configuration by running `yarn graphcommerce export-config` -## Extending the configuration in your project +## Extending the configuration in your project Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration. @@ -132,7 +132,9 @@ input GraphCommerceStorefrontConfig { domain: String """ - Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers + Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work. + + This value can be used as a sub-path identifier only, make sure linguiLocale is configured for each URL. """ locale: String! diff --git a/packagesDev/next-config/dist/config/demoConfig.js b/packagesDev/next-config/dist/config/demoConfig.js index 8c92c41ff4..fc2e14d78b 100644 --- a/packagesDev/next-config/dist/config/demoConfig.js +++ b/packagesDev/next-config/dist/config/demoConfig.js @@ -13,10 +13,25 @@ exports.demoConfig = { hygraphLocales: ['nl', 'en_us'], cartDisplayPricesInclTax: true, }, - { locale: 'fr-be', magentoStoreCode: 'fr_BE', cartDisplayPricesInclTax: true }, - { locale: 'nl-be', magentoStoreCode: 'nl_BE', cartDisplayPricesInclTax: true }, - { locale: 'en-gb', magentoStoreCode: 'en_GB', cartDisplayPricesInclTax: true }, - { locale: 'en-ca', magentoStoreCode: 'en_CA' }, + { + locale: 'fr-be', + magentoStoreCode: 'fr_BE', + cartDisplayPricesInclTax: true, + linguiLocale: 'fr', + }, + { + locale: 'nl-be', + magentoStoreCode: 'nl_BE', + cartDisplayPricesInclTax: true, + linguiLocale: 'nl', + }, + { + locale: 'en-gb', + magentoStoreCode: 'en_GB', + cartDisplayPricesInclTax: true, + linguiLocale: 'en', + }, + { locale: 'en-ca', magentoStoreCode: 'en_CA', linguiLocale: 'en' }, ], productFiltersPro: true, productFiltersLayout: 'DEFAULT', diff --git a/packagesDev/next-config/src/config/demoConfig.ts b/packagesDev/next-config/src/config/demoConfig.ts index 257f1cd171..2e7be0b305 100644 --- a/packagesDev/next-config/src/config/demoConfig.ts +++ b/packagesDev/next-config/src/config/demoConfig.ts @@ -15,10 +15,25 @@ export const demoConfig: PartialDeep; /** Add a gcms-locales header to make sure queries return in a certain language, can be an array to define fallbacks. */ hygraphLocales?: InputMaybe>; - /** Specify a custom locale for to load translations. */ + /** + * Specify a custom locale for to load translations. Must be lowercase valid locale. + * + * This value is also used for the Intl. + */ linguiLocale?: InputMaybe; - /** Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers */ + /** + * Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work. + * + * This value can be used as a sub-path identifier only, make sure linguiLocale is configured for each URL. + */ locale: Scalars['String']['input']; /** * Magento store code.