locale.ts
1 import type { Opt } from '@jet/environment'; 2 import { DEFAULT_STOREFRONT_CODE } from '~/constants/storefront'; 3 4 import type { 5 NormalizedLocale, 6 NormalizedStorefront, 7 NormalizedLanguage, 8 } from '@jet-app/app-store/api/locale'; 9 import type { Locale } from '@jet-app/app-store/foundation/dependencies/locale/locale'; 10 11 import { TEXT_DIRECTION } from '@amp/web-app-components/src/constants'; 12 import { getLocAttributes } from '@amp/web-apps-localization'; 13 14 import { regions } from '~/utils/storefront-data'; 15 import { getJet } from '~/jet/svelte'; 16 17 export type NormalizedLocaleWithDefault = NormalizedLocale & { 18 isDefaultLanguage: boolean; 19 }; 20 21 type LanguageDetails = { 22 languages: NormalizedLanguage[]; 23 defaultLanguage: NormalizedLanguage; 24 }; 25 26 export function normalizeStorefront(storefront: Opt<string>): { 27 storefront: NormalizedStorefront; 28 languages: NormalizedLanguage[]; 29 defaultLanguage: NormalizedLanguage; 30 } { 31 const storefronts: Record<NormalizedStorefront, LanguageDetails> = {}; 32 33 for (const { locales } of regions) { 34 for (const { id, language, isDefault } of locales) { 35 if (isDefault) { 36 storefronts[id as NormalizedStorefront] = { 37 languages: [], 38 defaultLanguage: language as NormalizedLanguage, 39 }; 40 } 41 42 if (id in storefronts) { 43 storefronts[id as NormalizedStorefront].languages.push( 44 language as NormalizedLanguage, 45 ); 46 } 47 } 48 } 49 50 const normalizedStorefront = (storefront || '').toLowerCase(); 51 const chosenStorefront = 52 normalizedStorefront in storefronts 53 ? (normalizedStorefront as NormalizedStorefront) 54 : DEFAULT_STOREFRONT_CODE; 55 56 return { 57 storefront: chosenStorefront, 58 ...storefronts[chosenStorefront], 59 }; 60 } 61 62 export function normalizeLanguage( 63 language: string, 64 languages: NormalizedLanguage[], 65 defaultLanguage: NormalizedLanguage, 66 ): { language: NormalizedLanguage; isDefaultLanguage: boolean } { 67 function annotateReturn(language: NormalizedLanguage): { 68 language: NormalizedLanguage; 69 isDefaultLanguage: boolean; 70 } { 71 return { 72 language, 73 isDefaultLanguage: language === defaultLanguage, 74 }; 75 } 76 77 // Prefer an exact match (ex. en-US matches en-US) 78 const exactMatch = findMatch(language, languages, (a, b) => a === b); 79 if (exactMatch) { 80 return annotateReturn(exactMatch); 81 } 82 83 // Try partial match (ex. fr-CA or fr matches fr-FR) 84 const partialMatch = findMatch( 85 language, 86 languages, 87 (a, b) => a.split('-')[0] === b.split('-')[0], 88 ); 89 if (partialMatch) { 90 return annotateReturn(partialMatch); 91 } 92 93 // The only remaining choice is the storefront default 94 return annotateReturn(defaultLanguage); 95 } 96 97 function findMatch<T extends string>( 98 needle: string, 99 haystack: T[], 100 matches: (a: string, b: string) => boolean, 101 ): Opt<T> { 102 return haystack.find((possibility) => 103 matches(possibility.toLowerCase(), needle.toLowerCase()), 104 ); 105 } 106 107 /** 108 * Gets the current Locale instance from the Svelte context. 109 * 110 * @return the active {@linkcode NormalizedLocale} 111 */ 112 export function getLocale(): NormalizedLocale { 113 let locale: Locale | undefined; 114 115 try { 116 const { objectGraph } = getJet(); 117 118 locale = objectGraph.locale; 119 } catch { 120 throw new Error('`getLocale` called before `Jet.load`'); 121 } 122 123 return { 124 storefront: locale.activeStorefront, 125 language: locale.activeLanguage, 126 }; 127 } 128 129 /** 130 * Returns whether or not the document is in RTL mode, first based on the document's direction, 131 * with a fallback to the storefronts default writing direction. 132 */ 133 export function isRtl() { 134 const { storefront } = getLocale(); 135 const { dir } = getLocAttributes(storefront); 136 137 return ( 138 (typeof document !== 'undefined' && 139 document.dir === TEXT_DIRECTION.RTL) || 140 dir === TEXT_DIRECTION.RTL 141 ); 142 }