{
  "version": 3,
  "sources": ["../../../../../src/lib/ui/hooks/useTranslation/useTranslation.tsx"],
  "sourcesContent": ["import { TLI18nMessage, getDeclaredMessages, useValue, warnOnce } from '@tldraw/editor'\nimport * as React from 'react'\nimport {\n\tIntlContext,\n\tIntlProvider,\n\tReactIntlErrorCode,\n\tcreateIntl,\n\tcreateIntlCache,\n\ttype IntlConfig,\n} from 'react-intl'\n// Nothing renders these, so nothing else imports them, and an app looks them up by key. Imported\n// so their English is declared \u2014 see `getDeclaredMessages`.\nimport '../../consumerMessages'\nimport { useAssetUrls } from '../../context/asset-urls'\nimport { TLUiTranslationKey } from './TLUiTranslationKey'\nimport { EN_TRANSLATION, TLUiTranslation, fetchTranslation } from './translations'\n\n/** @public */\nexport interface TLUiTranslationProviderProps {\n\tchildren: React.ReactNode\n\tlocale: string\n\t/**\n\t * A collection of overrides different locales.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * <TranslationProvider overrides={{ en: { 'style-panel.styles': 'Properties' } }} />\n\t * ```\n\t */\n\toverrides?: Record<string, Record<string, string>>\n}\n\n// What a nested `TldrawUiTranslationProvider` needs to tell its parent `IntlProvider` apart from\n// an app's: the catalog this one handed react-intl, which passes it through as `intl.messages`.\nconst OuterCatalogContext = React.createContext<{\n\tmessages: IntlConfig['messages']\n\tappMessages: IntlConfig['messages'] | undefined\n} | null>(null)\n\n/** @public */\nexport type TLUiTranslationContextType = TLUiTranslation\n\n/** @internal */\nexport const TranslationsContext = React.createContext<TLUiTranslationContextType | null>(null)\n\n/** @public */\nexport function useCurrentTranslation() {\n\tconst translations = React.useContext(TranslationsContext)\n\tif (!translations) {\n\t\tthrow new Error('useCurrentTranslation must be used inside of <TldrawUiContextProvider />')\n\t}\n\treturn translations\n}\n\n/**\n * Like {@link useCurrentTranslation}, but returns `null` instead of throwing when used outside\n * of a `<TldrawUiTranslationProvider />` / `<TldrawUiContextProvider />`.\n *\n * @public\n */\nexport function useMaybeCurrentTranslation() {\n\treturn React.useContext(TranslationsContext)\n}\n\n/**\n * Provides a translation context to the editor. Wrap this around components that use\n * `useTranslation` (such as `TldrawSelectionForeground`) when you don't want to use the\n * full `TldrawUiContextProvider`. Must be rendered inside an `AssetUrlsProvider`.\n *\n * @public @react\n */\nexport function TldrawUiTranslationProvider({\n\toverrides,\n\tlocale,\n\tchildren,\n}: TLUiTranslationProviderProps) {\n\tconst getAssetUrl = useAssetUrls()\n\n\t// Only the fetch belongs in an effect; merging overrides is derived from data already in hand.\n\t// Inside one, a changed override took another round trip to apply, and an app assembling its\n\t// overrides as it loads its own strings refetched this catalog on every identity change.\n\tconst [loaded, setLoaded] = React.useState<TLUiTranslation>(EN_TRANSLATION)\n\n\tReact.useEffect(() => {\n\t\tlet isCancelled = false\n\n\t\tasync function loadTranslation() {\n\t\t\tconst translation = await fetchTranslation(locale, getAssetUrl)\n\t\t\tif (isCancelled) return\n\t\t\tsetLoaded(translation)\n\t\t}\n\n\t\tloadTranslation()\n\n\t\treturn () => {\n\t\t\tisCancelled = true\n\t\t}\n\t}, [getAssetUrl, locale])\n\n\t// Keyed on the requested locale, not the loaded one. They differ when the SDK ships no catalogue\n\t// for it and `fetchTranslation` falls back to English \u2014 which is the case for a locale that\n\t// exists only in an app's own strings, so that's exactly when its overrides have to apply.\n\t//\n\t// Lowest to highest: the declared English, the English overrides, the locale's catalog, its\n\t// overrides. The English overrides go underneath the catalog, so they reach only ids it lacks:\n\t// an app's own key not yet translated for this locale falls back to its English, not its id.\n\tconst declared = useValue('declared messages', getDeclaredMessages, [])\n\tconst translated = React.useMemo(() => {\n\t\tconst enOverrides = locale === 'en' ? undefined : overrides?.en\n\t\treturn { ...enOverrides, ...loaded.messages, ...overrides?.[locale] }\n\t}, [loaded, overrides, locale])\n\n\tconst currentTranslation = React.useMemo(\n\t\t() => ({\n\t\t\t...loaded,\n\t\t\tmessages: { ...declared, ...translated } as TLUiTranslation['messages'],\n\t\t}),\n\t\t[loaded, declared, translated]\n\t)\n\n\t// react-intl builds each provider's catalog from its own props alone, so without merging this\n\t// one shadows an app's own `IntlProvider` for everything inside it \u2014 every tldraw.com dialog\n\t// and share menu \u2014 leaving them on `defaultMessage`: English, and silently untranslated\n\t// everywhere else. Ours wins a collision, since `overrides.translations` is the documented way\n\t// to change an SDK string. The declared English goes underneath the app's catalog, not over it:\n\t// it holds the app's own declarations too, whose English would otherwise shadow their\n\t// translations.\n\t//\n\t// Only the app's catalog is merged, never another of ours: `<Tldraw>` renders one of these\n\t// above the UI's, with the locale it mounted with, and merging that put its language under\n\t// every other \u2014 French under English, French filling German's gaps.\n\t//\n\t// The cast is because the type is a union of two uniform records and this one is mixed on\n\t// purpose: `formatMessage` dispatches per message, so the SDK's ICU strings and an app's\n\t// precompiled AST can share a catalog. That holds only while the app keeps react-intl's ICU\n\t// parser \u2014 an app that aliases it away, as tldraw.com used to, formats every string message\n\t// with a placeholder to its own source.\n\tconst parentIntl = React.useContext(IntlContext)\n\tconst outer = React.useContext(OuterCatalogContext)\n\tconst appMessages =\n\t\touter && parentIntl?.messages === outer.messages ? outer.appMessages : parentIntl?.messages\n\tconst messages = React.useMemo(\n\t\t() =>\n\t\t\tappMessages\n\t\t\t\t? ({\n\t\t\t\t\t\t...declared,\n\t\t\t\t\t\t...appMessages,\n\t\t\t\t\t\t...translated,\n\t\t\t\t\t} as IntlConfig['messages'])\n\t\t\t\t: currentTranslation.messages,\n\t\t[appMessages, declared, translated, currentTranslation.messages]\n\t)\n\tconst outerCatalog = React.useMemo(() => ({ messages, appMessages }), [messages, appMessages])\n\n\treturn (\n\t\t<TranslationsContext.Provider value={currentTranslation}>\n\t\t\t{/* One IntlShape behind both APIs: `msg()` by key and `<F>` by descriptor read the\n\t\t\t    same merged catalog, so an app's `overrides.translations` reaches both. */}\n\t\t\t<IntlProvider\n\t\t\t\tlocale={locale}\n\t\t\t\tdefaultLocale=\"en\"\n\t\t\t\tmessages={messages}\n\t\t\t\tonError={onTranslationError}\n\t\t\t\tfallbackOnEmptyString={false}\n\t\t\t>\n\t\t\t\t<OuterCatalogContext.Provider value={outerCatalog}>{children}</OuterCatalogContext.Provider>\n\t\t\t</IntlProvider>\n\t\t</TranslationsContext.Provider>\n\t)\n}\n\n/**\n * Values substituted into a message's ICU placeholders.\n *\n * @public\n */\nexport type TLUiTranslationValues = Record<\n\tstring,\n\tstring | number | bigint | boolean | Date | null | undefined\n>\n\n// `fallbackOnEmptyString` is off on both intl instances because an override of `''` is how an app\n// hides a label; react-intl's default renders the id in its place.\n//\n// Returning the key verbatim for an unknown id is documented behaviour: apps pass their own keys\n// and plain text through `msg()`. Only surface errors that mean a message is actually malformed.\nconst onTranslationError: IntlConfig['onError'] = (error) => {\n\tif (error.code === ReactIntlErrorCode.MISSING_TRANSLATION) return\n\tif (process.env.NODE_ENV !== 'production') console.error(error)\n}\n\n// One cache for the intl instances built below, as formatjs recommends, so that repeated mounts\n// don't leak a formatter cache each.\nconst intlCache = createIntlCache()\n\n/**\n * Returns a function to translate a message into a string based on the current translation.\n *\n * Pass a message declared with `defineMessages`, or a translation key. Messages are\n * {@link https://unicode-org.github.io/icu/userguide/format_parse/messages/ | ICU MessageFormat},\n * so a message can pluralise or select on the values you pass it. A declared message missing from\n * the catalog falls back to its `defaultMessage`; an unknown key is returned as-is.\n *\n * @example\n *\n * ```ts\n * const messages = defineMessages({\n * \tselected: { id: 'my-app.selected', defaultMessage: '{count, plural, one {# shape} other {# shapes}}' },\n * })\n *\n * const msg = useTranslation()\n * const label = msg(messages.selected, { count: 5 })\n * const title = msg('my-app.title')\n * ```\n *\n * @public\n */\nexport function useTranslation() {\n\tconst translation = React.useContext(TranslationsContext)\n\n\tReact.useEffect(() => {\n\t\tif (!translation?.messages) {\n\t\t\twarnOnce(\n\t\t\t\t'No translation messages found, falling back to default translation. Wrap your app in <TldrawUiContextProvider>, or in both <AssetUrlsProvider> and <TldrawUiTranslationProvider>, to provide translations.'\n\t\t\t)\n\t\t}\n\t}, [translation?.messages])\n\n\t// Not `useIntl()`: that throws outside a provider, where this hook still has to work.\n\tconst providedIntl = React.useContext(IntlContext)\n\n\t// `TranslationsContext` on its own has always been enough to translate \u2014 tests and hand-rolled\n\t// setups provide it without the rest. Keep that working by formatting against its messages when\n\t// there's no `IntlProvider` above us, rather than silently falling back to English.\n\tconst standaloneIntl = React.useMemo(\n\t\t() =>\n\t\t\tprovidedIntl\n\t\t\t\t? null\n\t\t\t\t: createIntl(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlocale: translation?.locale ?? 'en',\n\t\t\t\t\t\t\tdefaultLocale: 'en',\n\t\t\t\t\t\t\tmessages: translation?.messages ?? getDeclaredMessages(),\n\t\t\t\t\t\t\tonError: onTranslationError,\n\t\t\t\t\t\t\tfallbackOnEmptyString: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tintlCache\n\t\t\t\t\t),\n\t\t[providedIntl, translation?.locale, translation?.messages]\n\t)\n\tconst intl = providedIntl ?? standaloneIntl!\n\n\treturn React.useCallback(\n\t\tfunction msg(\n\t\t\tmessage?: Exclude<string, TLUiTranslationKey> | string | TLI18nMessage,\n\t\t\tvalues?: TLUiTranslationValues\n\t\t): string {\n\t\t\t// `formatMessage` needs an id. Callers pass optional labels straight through, so keep the\n\t\t\t// nullish passthrough this has always had rather than substituting an empty string.\n\t\t\tif (!message) return message as string\n\t\t\t// A bare key carries no default message, by design: this is the path for labels that\n\t\t\t// arrive as data and for an app's own keys.\n\t\t\treturn intl.formatMessage(typeof message === 'string' ? { id: message } : message, values)\n\t\t},\n\t\t[intl]\n\t)\n}\n\n/**\n * Returns the current text direction ('ltr' or 'rtl') based on the current translation.\n *\n * @public\n */\nexport function useDirection() {\n\tconst translation = useMaybeCurrentTranslation()\n\treturn translation?.dir ?? 'ltr'\n}\n\nexport function untranslated(string: string) {\n\treturn string as TLUiTranslationKey\n}\n"],
  "mappings": "AAsKI;AAtKJ,SAAwB,qBAAqB,UAAU,gBAAgB;AACvE,YAAY,WAAW;AACvB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AAGP,OAAO;AACP,SAAS,oBAAoB;AAE7B,SAAS,gBAAiC,wBAAwB;AAoBlE,MAAM,sBAAsB,MAAM,cAGxB,IAAI;AAMP,MAAM,sBAAsB,MAAM,cAAiD,IAAI;AAGvF,SAAS,wBAAwB;AACvC,QAAM,eAAe,MAAM,WAAW,mBAAmB;AACzD,MAAI,CAAC,cAAc;AAClB,UAAM,IAAI,MAAM,0EAA0E;AAAA,EAC3F;AACA,SAAO;AACR;AAQO,SAAS,6BAA6B;AAC5C,SAAO,MAAM,WAAW,mBAAmB;AAC5C;AASO,SAAS,4BAA4B;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACD,GAAiC;AAChC,QAAM,cAAc,aAAa;AAKjC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA0B,cAAc;AAE1E,QAAM,UAAU,MAAM;AACrB,QAAI,cAAc;AAElB,mBAAe,kBAAkB;AAChC,YAAM,cAAc,MAAM,iBAAiB,QAAQ,WAAW;AAC9D,UAAI,YAAa;AACjB,gBAAU,WAAW;AAAA,IACtB;AAEA,oBAAgB;AAEhB,WAAO,MAAM;AACZ,oBAAc;AAAA,IACf;AAAA,EACD,GAAG,CAAC,aAAa,MAAM,CAAC;AASxB,QAAM,WAAW,SAAS,qBAAqB,qBAAqB,CAAC,CAAC;AACtE,QAAM,aAAa,MAAM,QAAQ,MAAM;AACtC,UAAM,cAAc,WAAW,OAAO,SAAY,WAAW;AAC7D,WAAO,EAAE,GAAG,aAAa,GAAG,OAAO,UAAU,GAAG,YAAY,MAAM,EAAE;AAAA,EACrE,GAAG,CAAC,QAAQ,WAAW,MAAM,CAAC;AAE9B,QAAM,qBAAqB,MAAM;AAAA,IAChC,OAAO;AAAA,MACN,GAAG;AAAA,MACH,UAAU,EAAE,GAAG,UAAU,GAAG,WAAW;AAAA,IACxC;AAAA,IACA,CAAC,QAAQ,UAAU,UAAU;AAAA,EAC9B;AAmBA,QAAM,aAAa,MAAM,WAAW,WAAW;AAC/C,QAAM,QAAQ,MAAM,WAAW,mBAAmB;AAClD,QAAM,cACL,SAAS,YAAY,aAAa,MAAM,WAAW,MAAM,cAAc,YAAY;AACpF,QAAM,WAAW,MAAM;AAAA,IACtB,MACC,cACI;AAAA,MACD,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,IACJ,IACC,mBAAmB;AAAA,IACvB,CAAC,aAAa,UAAU,YAAY,mBAAmB,QAAQ;AAAA,EAChE;AACA,QAAM,eAAe,MAAM,QAAQ,OAAO,EAAE,UAAU,YAAY,IAAI,CAAC,UAAU,WAAW,CAAC;AAE7F,SACC,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,oBAGpC;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,eAAc;AAAA,MACd;AAAA,MACA,SAAS;AAAA,MACT,uBAAuB;AAAA,MAEvB,8BAAC,oBAAoB,UAApB,EAA6B,OAAO,cAAe,UAAS;AAAA;AAAA,EAC9D,GACD;AAEF;AAiBA,MAAM,qBAA4C,CAAC,UAAU;AAC5D,MAAI,MAAM,SAAS,mBAAmB,oBAAqB;AAC3D,MAAI,QAAQ,IAAI,aAAa,aAAc,SAAQ,MAAM,KAAK;AAC/D;AAIA,MAAM,YAAY,gBAAgB;AAwB3B,SAAS,iBAAiB;AAChC,QAAM,cAAc,MAAM,WAAW,mBAAmB;AAExD,QAAM,UAAU,MAAM;AACrB,QAAI,CAAC,aAAa,UAAU;AAC3B;AAAA,QACC;AAAA,MACD;AAAA,IACD;AAAA,EACD,GAAG,CAAC,aAAa,QAAQ,CAAC;AAG1B,QAAM,eAAe,MAAM,WAAW,WAAW;AAKjD,QAAM,iBAAiB,MAAM;AAAA,IAC5B,MACC,eACG,OACA;AAAA,MACA;AAAA,QACC,QAAQ,aAAa,UAAU;AAAA,QAC/B,eAAe;AAAA,QACf,UAAU,aAAa,YAAY,oBAAoB;AAAA,QACvD,SAAS;AAAA,QACT,uBAAuB;AAAA,MACxB;AAAA,MACA;AAAA,IACD;AAAA,IACH,CAAC,cAAc,aAAa,QAAQ,aAAa,QAAQ;AAAA,EAC1D;AACA,QAAM,OAAO,gBAAgB;AAE7B,SAAO,MAAM;AAAA,IACZ,SAAS,IACR,SACA,QACS;AAGT,UAAI,CAAC,QAAS,QAAO;AAGrB,aAAO,KAAK,cAAc,OAAO,YAAY,WAAW,EAAE,IAAI,QAAQ,IAAI,SAAS,MAAM;AAAA,IAC1F;AAAA,IACA,CAAC,IAAI;AAAA,EACN;AACD;AAOO,SAAS,eAAe;AAC9B,QAAM,cAAc,2BAA2B;AAC/C,SAAO,aAAa,OAAO;AAC5B;AAEO,SAAS,aAAa,QAAgB;AAC5C,SAAO;AACR;",
  "names": []
}
