Ctrl K
ComunidadGuías…

src/services/formatLocale.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { getActivePinia } from 'pinia'
import { i18n } from '~/modules/i18n'
import { useMainStore } from '~/stores/main'

export const DEFAULT_FORMAT_LOCALE = 'en-GB'

const FORMAT_SAMPLE_DATE = new Date(2026, 6, 13)
const FORMAT_SAMPLE_NUMBER = 1234567.89

export const FORMAT_LOCALE_REGIONS = [
  { countryCode: 'AE', locale: 'ar-AE' },
  { countryCode: 'AR', locale: 'es-AR' },
  { countryCode: 'AT', locale: 'de-AT' },
  { countryCode: 'AU', locale: 'en-AU' },
  { countryCode: 'BE', locale: 'nl-BE' },
  { countryCode: 'BR', locale: 'pt-BR' },
  { countryCode: 'CA', locale: 'en-CA' },
  { countryCode: 'CH', locale: 'de-CH' },
  { countryCode: 'CL', locale: 'es-CL' },
  { countryCode: 'CN', locale: 'zh-CN' },
  { countryCode: 'CO', locale: 'es-CO' },
  { countryCode: 'DE', locale: 'de-DE' },
  { countryCode: 'DK', locale: 'da-DK' },
  { countryCode: 'ES', locale: 'es-ES' },
  { countryCode: 'FI', locale: 'fi-FI' },
  { countryCode: 'FR', locale: 'fr-FR' },
  { countryCode: 'GB', locale: 'en-GB' },
  { countryCode: 'HK', locale: 'en-HK' },
  { countryCode: 'ID', locale: 'id-ID' },
  { countryCode: 'IE', locale: 'en-IE' },
  { countryCode: 'IL', locale: 'en-IL' },
  { countryCode: 'IN', locale: 'en-IN' },
  { countryCode: 'IT', locale: 'it-IT' },
  { countryCode: 'JP', locale: 'ja-JP' },
  { countryCode: 'KR', locale: 'ko-KR' },
  { countryCode: 'MX', locale: 'es-MX' },
  { countryCode: 'NL', locale: 'nl-NL' },
  { countryCode: 'NO', locale: 'nb-NO' },
  { countryCode: 'NZ', locale: 'en-NZ' },
  { countryCode: 'PL', locale: 'pl-PL' },
  { countryCode: 'PT', locale: 'pt-PT' },
  { countryCode: 'RU', locale: 'ru-RU' },
  { countryCode: 'SA', locale: 'ar-SA-u-ca-gregory-nu-latn' },
  { countryCode: 'SE', locale: 'sv-SE' },
  { countryCode: 'SG', locale: 'en-SG' },
  { countryCode: 'TH', locale: 'th-TH-u-ca-gregory-nu-latn' },
  { countryCode: 'TR', locale: 'tr-TR' },
  { countryCode: 'TW', locale: 'zh-TW' },
  { countryCode: 'UA', locale: 'uk-UA' },
  { countryCode: 'US', locale: 'en-US' },
  { countryCode: 'VN', locale: 'vi-VN' },
  { countryCode: 'ZA', locale: 'en-ZA' },
] as const

export interface FormatLocaleOption {
  label: string
  value: string
}

function getCanonicalLocale(locale: string) {
  try {
    return Intl.getCanonicalLocales(locale)[0] ?? null
  }
  catch {
    return null
  }
}

export function normalizeFormatLocale(locale?: string | null) {
  const canonical = getCanonicalLocale(String(locale || '').trim())
  if (!canonical)
    return null

  const hasDateSupport = Intl.DateTimeFormat.supportedLocalesOf([canonical]).length > 0
  const hasNumberSupport = Intl.NumberFormat.supportedLocalesOf([canonical]).length > 0
  return hasDateSupport && hasNumberSupport ? canonical : null
}

// Regional convention used when the account has no explicit format_locale, so a French UI shows French numbers.
const UI_LANGUAGE_FORMAT_LOCALES: Record<string, string> = {
  'de': 'de-DE',
  'es': 'es-ES',
  'fr': 'fr-FR',
  'hi': 'en-IN',
  'id': 'id-ID',
  'it': 'it-IT',
  'ja': 'ja-JP',
  'ko': 'ko-KR',
  'pl': 'pl-PL',
  'pt-br': 'pt-BR',
  'ru': 'ru-RU',
  'tr': 'tr-TR',
  'vi': 'vi-VN',
  'zh-cn': 'zh-CN',
}

export function getUiFormatLocale() {
  return UI_LANGUAGE_FORMAT_LOCALES[String(i18n.global.locale.value)] ?? DEFAULT_FORMAT_LOCALE
}

export function resolveFormatLocale(locale?: string | null) {
  return normalizeFormatLocale(locale) ?? getUiFormatLocale()
}

function getAccountFormatLocale() {
  if (!getActivePinia())
    return null

  return useMainStore().user?.format_locale ?? null
}

export function getFormatLocale() {
  return resolveFormatLocale(getAccountFormatLocale())
}

export function formatNumber(value: number | bigint, options?: Intl.NumberFormatOptions) {
  return new Intl.NumberFormat(getFormatLocale(), options).format(value)
}

export function formatNumberValue(value: number | null | undefined, options?: Intl.NumberFormatOptions) {
  return formatNumber(Number(value ?? 0), options)
}

export function getFormatLocaleOptions(displayLanguage?: string | null): FormatLocaleOption[] {
  const language = String(displayLanguage || i18n.global.locale.value || 'en')
  const regionNames = typeof Intl.DisplayNames === 'function'
    ? new Intl.DisplayNames([language], { type: 'region' })
    : null

  return FORMAT_LOCALE_REGIONS.map(({ countryCode, locale }) => {
    const countryName = regionNames?.of(countryCode) ?? countryCode
    const dateSample = new Intl.DateTimeFormat(locale).format(FORMAT_SAMPLE_DATE)
    const numberSample = new Intl.NumberFormat(locale).format(FORMAT_SAMPLE_NUMBER)

    return {
      label: `${countryName} - ${dateSample} - ${numberSample}`,
      value: locale,
    }
  }).sort((left, right) => left.label.localeCompare(right.label, language))
}