shadimage
preskok
uilocale-contextsource: ok · refreshed

Locale Context

Locale context provider for internationalization support

npx shadcn add "https://ui-three-mu.vercel.app/r/locale-context.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
react-number-format

Registry dependencies

https://ui-three-mu.vercel.app/r/create-ctx.json

Files3 · source cached 10/8/2026, 6:48:13 AM

registry/preskok/ui/preskok-ui/locale-context/locale-context.tsx
"use client"

import type { PropsWithChildren } from "react"
import React, { useState } from "react"

import { createCtx } from "@/lib/create-ctx"

import { getCurrencyCodeOrSymbol } from "../number-format/currency-helpers"
import { DEFAULT_LOCALE, Locale } from "./locale-helpers"

const splitCurrentUrl = () => {
  return {
    origin: window.location.origin,
    hostname: window.location.hostname,
    pathname: window.location.pathname,
    search: window.location.search,
    hash: window.location.hash,
  }
}

const retrieveLocaleFromUrlStrict = (passedUrl?: string): Locale | null => {
  const url = passedUrl || splitCurrentUrl().pathname
  const availableLocalesArray: Array<Locale> = Object.values(Locale)
  const supposedUrlLocale = url.split("/")[1] as Locale | undefined

  return supposedUrlLocale && availableLocalesArray.includes(supposedUrlLocale)
    ? supposedUrlLocale
    : null
}

type CurrencyCodeOrSymbolExpectedOutputOptions =
  | "currency_code"
  | "currency_symbol"

interface GetCurrencyCodeOrSymbolParams {
  inputValue: string
  expectedOutput?: CurrencyCodeOrSymbolExpectedOutputOptions
}

interface LocaleContextProviderProps {
  globalLocale: Locale
  setGlobalLocale: (localeOption: Locale) => void
}

export const [useLocaleContext, LocaleContextProvider] =
  createCtx<LocaleContextProviderProps>()

export const LocaleContext: React.FC<PropsWithChildren> = ({ children }) => {
  const [locale, setLocale] = useState<Locale>(() => {
    if (typeof window === "undefined") {
      return DEFAULT_LOCALE
    }

    return retrieveLocaleFromUrlStrict() ?? DEFAULT_LOCALE
  })

  const setGlobalLocale = (localeOption: Locale): void => {
    setLocale(localeOption)
  }

  /**
   * This function will return the currency code or symbol code based on the input value and the expected output.
   *
   * IMPORTANT: It currently does not take into the account the locale value due to the fact we have a problem in business
   * logic and we are using the '/en/' or '/sl/' in the URL to reflect the locale which is incorrect. We should be using the
   * BCP47 standard which would equate to '/en-US/' or '/en-EN/' or '/en-GB/' or '/sl-SI/'.
   */
  const getCurrencyCodeOrSymbolCode = ({
    inputValue,
    expectedOutput = "currency_code",
  }: GetCurrencyCodeOrSymbolParams) => {
    return getCurrencyCodeOrSymbol({ inputValue, expectedOutput })
  }

  const memoizedContextProps = {
    globalLocale: locale,
    setGlobalLocale,
    getCurrencyCodeOrSymbolCode: getCurrencyCodeOrSymbolCode,
  }

  return (
    <LocaleContextProvider value={memoizedContextProps}>
      {children}
    </LocaleContextProvider>
  )
}
registry/preskok/ui/preskok-ui/locale-context/locale-helpers.ts
import type { NumericFormatProps } from "react-number-format"

export const Locale = {
  DE: "de",
  EN: "en",
  FR: "fr",
  IT: "it",
  HR: "hr",
  SL: "sl",
} as const

export type Locale = (typeof Locale)[keyof typeof Locale]

export const LocaleToBcp47Map = {
  de: "de-DE",
  en: "en-EN",
  fr: "fr-FR",
  it: "it-IT",
  hr: "hr-HR",
  sl: "sl-SL",
} as const

export type LocaleToBcp47Map =
  (typeof LocaleToBcp47Map)[keyof typeof LocaleToBcp47Map]

export const CurrencyCodeAndSymbolMap = {
  USD: "$",
  EUR: "€",
  GBP: "£",
} as const

export type CurrencyCodeAndSymbolMap =
  (typeof CurrencyCodeAndSymbolMap)[keyof typeof CurrencyCodeAndSymbolMap]

export const Bcp47CountryLanguageCodeToCurrencyCodeMap = {
  "en-US": "USD",
  "en-GB": "GBP",
  "en-EN": "EUR",
  "de-DE": "EUR",
  "de-AT": "EUR",
  "de-CH": "EUR",
  "el-GR": "EUR",
  "en-IE": "EUR",
  "fr-FR": "EUR",
  "it-IT": "EUR",
  "es-ES": "EUR",
  "pt-PT": "EUR",
  "sk-SK": "EUR",
  "sv-SE": "EUR",
  "da-DK": "EUR",
  "fi-FI": "EUR",
  "cs-CZ": "EUR",
  "it-CH": "EUR",
  "nl-NL": "EUR",
  "hu-HU": "EUR",
  "fr-BE": "EUR",
  "nl-BE": "EUR",
} as const

export type Bcp47CountryLanguageCodeToCurrencyCodeMap =
  (typeof Bcp47CountryLanguageCodeToCurrencyCodeMap)[keyof typeof Bcp47CountryLanguageCodeToCurrencyCodeMap]

export const DEFAULT_LOCALE = Locale.SL
export const DEFAULT_NUMBER_OF_DECIMALS = 2
export const DEFAULT_INVALID_NUMBER_TEXT = "Invalid number"
export const MAXIMUM_NUMBER_OF_DIGITS_ALLOWED = 12

export const LOCALE_TO_BCP_47_MAP: Record<Locale, LocaleToBcp47Map> = {
  de: "de-DE",
  en: "en-EN",
  fr: "fr-FR",
  it: "it-IT",
  hr: "hr-HR",
  sl: "sl-SL",
}

export const DEFAULT_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  displayType: "text",
  thousandSeparator: ".",
  decimalSeparator: ",",
  allowNegative: true,
  decimalScale: DEFAULT_NUMBER_OF_DECIMALS,
}

export const SLOVENE_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...DEFAULT_NUMERIC_FORMAT_OPTIONS,
  thousandSeparator: ".",
  decimalSeparator: ",",
}

export const GERMAN_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...SLOVENE_NUMERIC_FORMAT_OPTIONS,
}

export const ITALIAN_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...SLOVENE_NUMERIC_FORMAT_OPTIONS,
}

export const CROATIAN_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...SLOVENE_NUMERIC_FORMAT_OPTIONS,
}

export const FRENCH_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...DEFAULT_NUMERIC_FORMAT_OPTIONS,
  thousandSeparator: " ",
  decimalSeparator: ",",
}

export const ENGLISH_NUMERIC_FORMAT_OPTIONS: NumericFormatProps = {
  ...DEFAULT_NUMERIC_FORMAT_OPTIONS,
  thousandSeparator: ",",
  decimalSeparator: ".",
}

export const LOCALE_TO_NUMERIC_FORMAT_OPTIONS_MAP: Record<
  Locale,
  NumericFormatProps
> = {
  de: GERMAN_NUMERIC_FORMAT_OPTIONS,
  en: ENGLISH_NUMERIC_FORMAT_OPTIONS,
  fr: FRENCH_NUMERIC_FORMAT_OPTIONS,
  it: ITALIAN_NUMERIC_FORMAT_OPTIONS,
  hr: CROATIAN_NUMERIC_FORMAT_OPTIONS,
  sl: SLOVENE_NUMERIC_FORMAT_OPTIONS,
}
registry/preskok/ui/preskok-ui/number-format/currency-helpers.ts
import { CurrencyCodeAndSymbolMap } from "../locale-context/locale-helpers"

type CurrencyCodeOrSymbolExpectedOutputOptions =
  | "currency_code"
  | "currency_symbol"

interface GetCurrencyCodeOrSymbolParams {
  inputValue: string
  expectedOutput?: CurrencyCodeOrSymbolExpectedOutputOptions
}

/**
 * getCurrencyCodeOrSymbol is a helper function to get currency code or symbol from a string value. If no match is found
 * we return an empty string.
 *
 * @param {string} inputValue -> string value that might be a currency code or symbol.
 * @param {'currency_code' | 'currency_symbol'} expectedOutput -> optional expected output type (default: 'currency_code').
 * @returns {string} - currency code or symbol or an empty string if no match is found
 */
export const getCurrencyCodeOrSymbol = ({
  inputValue,
  expectedOutput = "currency_code",
}: GetCurrencyCodeOrSymbolParams): string => {
  let currencyCodeOrSymbolStringValue = ""
  const trimmedAndLowerCaseInput = inputValue.trim().toLowerCase()

  const currencyCodeOrSymbolLookup = Object.keys(CurrencyCodeAndSymbolMap).find(
    (key) =>
      key.toLowerCase() === trimmedAndLowerCaseInput ||
      CurrencyCodeAndSymbolMap[
        key as keyof typeof CurrencyCodeAndSymbolMap
      ].toLowerCase() === trimmedAndLowerCaseInput
  )

  if (currencyCodeOrSymbolLookup) {
    if (expectedOutput === "currency_code") {
      currencyCodeOrSymbolStringValue = currencyCodeOrSymbolLookup
    } else {
      currencyCodeOrSymbolStringValue =
        CurrencyCodeAndSymbolMap[
          currencyCodeOrSymbolLookup as keyof typeof CurrencyCodeAndSymbolMap
        ]
    }
  }

  return currencyCodeOrSymbolStringValue
}

More from preskok

Browse all components in this library →