import IntlMessageFormat from 'intl-messageformat' ; import merge from 'lodash/merge' ; import React from 'react' ; import { Strings } from './iface' ; import ar from './langs/ar' ; import en from './langs/en' ; import es from './langs/es' ; import fr from './langs/fr' ; import hi from './langs/hi' ; import pt from './langs/pt' ; import tr from './langs/tr' ; import zh from './langs/zh' ; const LOCAL_STORAGE_KEY = 'selectedLanguage' ; const QUERY_PARAM = 'lang' ; export const LANGUAGES = { en , ar : merge ( { } , en , ar ) , es : merge ( { } , en , es ) , fr : merge ( { } , en , fr ) , hi : merge ( { } , en , hi ) , pt : merge ( { } , en , pt ) , tr : merge ( { } , en , tr ) , zh : merge ( { } , en , zh ) , } ; export type Language = keyof typeof LANGUAGES ; export const LANGUAGE_KEYS = Object . keys ( LANGUAGES ) as Language [ ] ; export const isValidLanguage = ( lang ) : lang is Language => ! ! ( lang && lang in LANGUAGES ) ; class I18nManager { private currentLanguage : Language | null = null ; private listeners = new Set < ( lang : Language ) => void > ( ) ; public constructor ( ) { window . addEventListener ( 'storage' , e => { if ( e . key === LOCAL_STORAGE_KEY ) { this . checkLanguage ( ) ; } } ) ; } private checkLanguage ( ) { const oldLanguage = this . currentLanguage ; this . currentLanguage = null ; const newLanguage = this . getLanguage ( ) ; if ( oldLanguage !== newLanguage ) { this . listeners . forEach ( l => l ( newLanguage ) ) ; } } private calculateLanguage = ( ) : Language => { const selectedLanguage = localStorage . getItem ( LOCAL_STORAGE_KEY ) ; if ( isValidLanguage ( selectedLanguage ) ) { return selectedLanguage ; } if ( URLSearchParams ) { const params = new URLSearchParams ( window . location . search ) ; const queryLanguage = params . get ( QUERY_PARAM ) ; if ( isValidLanguage ( queryLanguage ) ) { return queryLanguage ; } } const supportedBrowserLanguages = window . navigator . languages . map ( l => l . split ( '-' ) [ 0 ] ) . filter ( isValidLanguage ) ; if ( supportedBrowserLanguages . length > 0 ) { return supportedBrowserLanguages [ 0 ] ; } return 'en' ; } ; public getLanguage = ( ) : Language => { if ( ! this . currentLanguage ) { this . currentLanguage = this . calculateLanguage ( ) ; } return this . currentLanguage ; } ; public setLanguage = ( language ) => { localStorage . setItem ( LOCAL_STORAGE_KEY , language ) ; if ( URLSearchParams && window . history . replaceState ) { const params = new URLSearchParams ( window . location . search ) ; params . set ( QUERY_PARAM , language ) ; window . history . replaceState ( null , '' , `?${ params . toString ( ) }` ) ; } this . checkLanguage ( ) ; } ; public addListener = ( listener ) => { this . listeners . add ( listener ) ; } ; public removeListener = ( listener ) => { this . listeners . delete ( listener ) ; } ; } const manager = new I18nManager ( ) ; export const { getLanguage } = manager ; export const { setLanguage } = manager ; export const { addListener } = manager ; export const { removeListener } = manager ; interface Placeholders { [ key ] : ( key : number ) => JSX . Element ; } interface FormatValues { [ key ] : number | string ; } const placeholderExtraction = /^([^{]*)\{(\w+)\}(.*)/ ; export function t < T > ( lang , extract ) ; export function t ( lang , extract , placeholders ) ; export function t ( lang , extract , placeholders ? ) { const str = extract ( LANGUAGES [ lang ] . strings ) ; if ( ! placeholders ) { return str ; } const components = [ ] ; let next = str ; while ( next ) { const e = placeholderExtraction . exec ( next ) ; if ( ! e ) { components . push ( < span key = { components . length } > { next } < /span>); next = null ; } else { const [ , prev , placeholderTag , remaining ] = e ; components . push ( < span key = { components . length } > { prev } < /span>); const component = placeholders [ placeholderTag ] ; if ( ! component ) { console . log ( 'Unknown i18n placeholder:' , placeholderTag ) ; } components . push ( component ( components . length ) || ( < span style = { { color : '#f00' } } > ERROR < /span>)); next = remaining ; } } return components ; } const formatCache = new Map < string , Map < string , IntlMessageFormat > > ( ) ; export const format = ( lang , extract , values ) => { const str = extract ( LANGUAGES [ lang ] . strings ) ; let langCache = formatCache . get ( lang ) ; if ( ! langCache ) { formatCache . set ( lang , ( langCache = new Map ( ) ) ) ; } let cache = langCache . get ( str ) ; if ( ! cache ) { langCache . set ( str , ( cache = new IntlMessageFormat ( str , lang ) ) ) ; } return cache . format ( values ) as string ; } ; export const canonicalUrl = ( lang ) => { const url = new URL ( window . location . href ) ; url . search = '' ; url . searchParams . set ( QUERY_PARAM , lang ) ; return url . href ; } ; export const getMeta = ( lang ) => LANGUAGES [ lang ] . meta ;