All files / src/hooks/useLanguage useLanguage.ts

79.41% Statements 27/34
78.94% Branches 15/19
87.5% Functions 7/8
78.12% Lines 25/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90              1x   1x   1x 14x   1x   5x 1x       4x 4x 4x           3x                           1x 5x 5x 5x 5x 5x     5x 5x       5x 5x             5x 5x             5x                             5x    
import { useCallback, useEffect, useMemo, useState } from "react";
import { useLocation, useHistory } from "react-router-dom";
import { Language, TEMP_SUPPORTED_LANGUAGES } from "../../constants/languages";
import { QUERY_PARAMS } from "../../constants/url";
import { useQueryParams } from "../../hooks/useQueryParams";
 
// Only supported language atm
const defaultLang = Language.TypeScript;
 
const LOCAL_KEY = "preferred-language";
 
const isValidLang = (lang?: string | Language): lang is Language =>
  lang != null && TEMP_SUPPORTED_LANGUAGES.has(lang as Language);
 
const getInitialLang = (langFromParams: string | Language): Language => {
  // First, use language from query params in url
  if (isValidLang(langFromParams)) {
    return langFromParams;
  }
 
  // Next check for one stored in localStorage
  try {
    const storedLang = (localStorage.getItem(LOCAL_KEY) ?? "") as Language;
    if (isValidLang(storedLang)) return storedLang;
  } catch {
    // Do nothing, we just don't want to crash if localStorage access is blocked.
  }
 
  // Otherwise fallback to a default
  return defaultLang;
};
 
export interface UseLanguageOptions {
  /**
   * Syncs the preferred language to a query param in URL
   */
  updateUrl?: boolean;
  /**
   * Saves the selected language to localStorage on select
   */
  updateSaved?: boolean;
}
 
export const useLanguage = (options: UseLanguageOptions = {}) => {
  const { updateUrl, updateSaved } = options;
  const { pathname, hash } = useLocation();
  const { replace } = useHistory();
  const params = useQueryParams();
  const langFromParams = params.get(QUERY_PARAMS.LANGUAGE) as Language;
 
  // Passed as function to guarantee it runs on hook mount
  const [language, setLanguage] = useState<Language>(() =>
    getInitialLang(langFromParams)
  );
 
  // State subscribes to query param changes
  useEffect(() => {
    Iif (isValidLang(langFromParams) && langFromParams !== language) {
      setLanguage(langFromParams);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [langFromParams]);
 
  // Syncs language changes to URL if updateUrl = true
  useEffect(() => {
    Iif (langFromParams !== language && updateUrl) {
      params.set(QUERY_PARAMS.LANGUAGE, language);
      replace({ pathname, hash, search: params.toString() });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [language, updateUrl]);
 
  const update = useCallback(
    (val: Language) => {
      setLanguage(val);
 
      if (updateSaved) {
        try {
          localStorage.setItem(LOCAL_KEY, val);
        } catch {
          // OK to fail silently
        }
      }
    },
    [updateSaved]
  );
 
  return useMemo(() => [language, update] as const, [language, update]);
};