Skip to main content
Glama
useDictionary.ts6.22 kB
import configuration from '@intlayer/config/built'; import type { DeclaredLocales, Dictionary, LocalesValues, } from '@intlayer/types'; import { type ComputedRef, computed, defineComponent, h, inject, isRef, type MaybeRefOrGetter, markRaw, ref, shallowRef, toValue, watch, } from 'vue'; import { getDictionary } from '../getDictionary'; import type { DeepTransformContent } from '../plugins'; import { INTLAYER_SYMBOL, type IntlayerProvider } from './installIntlayer'; export const atPath = (obj: any, path: (string | number)[]) => path.reduce((acc, k) => (acc == null ? undefined : acc[k as any]), obj); export const isObjectLike = (v: any) => v != null && typeof v === 'object'; export const isComponentLike = (v: any) => typeof v === 'function' || (isObjectLike(v) && ('render' in v || 'setup' in v)); /** Check if value is an IntlayerNode */ export const isIntlayerNode = (v: any): boolean => v != null && typeof v === 'object' && '__update' in v && 'render' in v && 'raw' in v; /** Wrap a getter into a lightweight functional component */ export const toComponent = (getter: () => any) => markRaw( defineComponent({ name: 'IntlayerLeaf', setup() { return () => { const v = getter(); if (v == null) return null; if (isComponentLike(v)) return h(v as any); // Render primitives/strings/arrays as text/children return Array.isArray(v) ? h('span', v as any) : v; }; }, }) ); /** * Create a proxy for IntlayerNode leaves that: * - Is ref-like (__v_isRef) so Vue tracks reactivity and unwraps in templates * - .value returns the IntlayerNode (for Vue's component :is="..." and ref unwrapping) * - .raw returns the primitive string (for script access) * - Passes through other IntlayerNode properties (render, toString, etc.) */ export const createIntlayerLeafProxy = (leafRef: ComputedRef<any>) => { const handler: ProxyHandler<any> = { get(_t, prop: any) { const node = leafRef.value; // Make this ref-like so Vue auto-unwraps and tracks reactivity if (prop === '__v_isRef') return true; // .value returns the IntlayerNode for Vue's ref unwrapping (component :is="..." usage) if (prop === 'value') { return node; } // $raw returns the underlying computed ref if (prop === '$raw') { return leafRef; } // Prevent Vue from making this reactive if (prop === '__v_skip') return true; // Coerce to component if (prop === 'c' || prop === 'asComponent') { return toComponent(() => leafRef.value); } // Pass through to IntlayerNode (including .raw for primitive access) if (node == null) return undefined; const val = node[prop]; return typeof val === 'function' ? val.bind(node) : val; }, // Make Object.keys() work ownKeys() { const node = leafRef.value; return node != null && typeof node === 'object' ? Reflect.ownKeys(node) : []; }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true }; }, }; return new Proxy({}, handler); }; export const useDictionary = < T extends Dictionary, L extends LocalesValues = DeclaredLocales, >( dictionary: MaybeRefOrGetter<T>, locale?: MaybeRefOrGetter<L> ) => { const intlayer = inject<IntlayerProvider>(INTLAYER_SYMBOL); // normalize provider locale const providerLocale = isRef(intlayer?.locale) ? intlayer.locale : ref(intlayer?.locale ?? configuration.internationalization.defaultLocale); // which locale to use (reactive) const localeTarget = computed<LocalesValues>(() => { const explicit = locale !== undefined ? toValue(locale) : undefined; return (explicit ?? providerLocale.value)!; }); // single reactive source for the entire content tree const source = shallowRef<any>({}); watch( [() => toValue(dictionary) as T, () => localeTarget.value], ([newDictionary, locale]) => { source.value = getDictionary(newDictionary, locale); }, { immediate: true, flush: 'sync' } ); // create a deep, read-only reactive proxy const makeProxy = (path: (string | number)[]) => { const leafRef: ComputedRef<any> = computed(() => atPath(source.value, path) ); const handler: ProxyHandler<any> = { get(_t, prop: any, _r) { // Make the proxy "ref-like" so templates unwrap {{proxy}} to its current value. if (prop === '__v_isRef') return true; if (prop === 'value') return leafRef.value; // Avoid Promise-like traps if (prop === 'then') return undefined; // Coerce the node to a component when asked if (prop === 'c' || prop === 'asComponent') return toComponent(() => leafRef.value); // Handy escape hatch to get the underlying computed if (prop === '$raw') return leafRef; // Primitive coercion in string contexts (e.g., `${node}`) if (prop === Symbol.toPrimitive) { return () => leafRef.value as any; } // Dive into children reactively const nextPath = path.concat(prop as any); const snapshot = atPath(source.value, nextPath); if (isObjectLike(snapshot) && !isComponentLike(snapshot)) { return makeProxy(nextPath); // nested proxy } // For IntlayerNode leaves, use special proxy for nice .value access if (isIntlayerNode(snapshot)) { return createIntlayerLeafProxy( computed(() => atPath(source.value, nextPath)) ); } // For other component-like things or primitives, return computed ref return computed(() => atPath(source.value, nextPath)); }, // Make Object.keys(), for...in, v-for on object keys work ownKeys() { const v = leafRef.value; return isObjectLike(v) ? Reflect.ownKeys(v) : []; }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true }; }, }; return new Proxy({}, handler); }; return makeProxy([]) as unknown as DeepTransformContent<T['content'], L>; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server