/ src / utils / locale.ts
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  }