@magic-translate/vue 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +85 -67
- package/dist/index.js.map +1 -1
- package/dist/magic-translate.d.ts +9 -3
- package/dist/translate.d.ts +25 -1
- package/dist/use-translate.d.ts +3 -0
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
Vue client for [Magic Translate](https://magictranslate.io). Write your
|
|
7
7
|
templates in one language and Magic Translate translates them on the fly.
|
|
8
8
|
|
|
9
|
+
No translation keys and no language files: the text stays in your templates,
|
|
10
|
+
and adding a language is one line.
|
|
11
|
+
|
|
9
12
|
Requires Vue 3.3 or newer. Works with Nuxt too.
|
|
10
13
|
|
|
14
|
+
Full docs: [magictranslate.io/docs/vue](https://magictranslate.io/docs/vue)
|
|
15
|
+
|
|
11
16
|
## Install
|
|
12
17
|
|
|
13
18
|
```bash
|
|
@@ -100,6 +105,34 @@ const t = useTranslate();
|
|
|
100
105
|
`t` returns the original text until the translation has loaded, and the
|
|
101
106
|
component re-renders once it has.
|
|
102
107
|
|
|
108
|
+
### Variables
|
|
109
|
+
|
|
110
|
+
Pass values that change, such as names or counts, in `v` and reference them
|
|
111
|
+
as `$name`. The text is translated once as a template, `Hi {0}, you have {1}
|
|
112
|
+
new messages`, and cached, however many different values it is shown with. The
|
|
113
|
+
values themselves are never sent to the API.
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<T :v="{ name: user.name, count }">Hi $name, you have $count new messages</T>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A translation can move the values to wherever the language needs them, and the
|
|
120
|
+
text around them can contain HTML. When only the values change, the cached
|
|
121
|
+
translation is reused and no request is made.
|
|
122
|
+
|
|
123
|
+
- Only keys of `v` are replaced, so text like `$5` stays as it is. If two keys
|
|
124
|
+
start the same way, the longest one wins.
|
|
125
|
+
- Values are inserted as text, never as HTML. Put markup in the text around
|
|
126
|
+
them instead: `Hi <b>$name</b>`.
|
|
127
|
+
- Variables are replaced in text only, not in attributes such as `href`.
|
|
128
|
+
- A key that is not found in the text logs a warning.
|
|
129
|
+
|
|
130
|
+
`t` and `translate` take the values in `v` as well:
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<input :placeholder="t('Search $count products', { v: { count } })" />
|
|
134
|
+
```
|
|
135
|
+
|
|
103
136
|
### Options per translation
|
|
104
137
|
|
|
105
138
|
Both the component and `t` can override the target language and the formality:
|
|
@@ -183,7 +216,9 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
183
216
|
```
|
|
184
217
|
|
|
185
218
|
Translations are only loaded in the browser. With server-side rendering the
|
|
186
|
-
server renders the original content, and it is translated after hydration.
|
|
219
|
+
server renders the original content, and it is translated after hydration. A
|
|
220
|
+
`<T>` with `v` renders empty on the server, so `$name` never shows, and
|
|
221
|
+
appears once the app runs in the browser.
|
|
187
222
|
|
|
188
223
|
## License
|
|
189
224
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@magic-translate/core"),t=require("vue");function n(e){let t=e.cloneNode(!0),n=[],r,i=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT);for(let e=i.nextNode();e;e=i.nextNode()){if(e.nodeType===Node.COMMENT_NODE){n.push(e);continue}let t=e;for(let{name:e}of Array.from(t.attributes))e.startsWith(`data-v-`)&&(r=e,t.removeAttribute(e))}n.forEach(e=>e.parentNode?.removeChild(e));let[,a,o,s]=/^(\s*)([\s\S]*?)(\s*)$/.exec(t.innerHTML);return{html:o,leadingWhitespace:a,trailingWhitespace:s,scopeAttribute:r}}function r(e){if(Array.isArray(e))for(let n of e){if(!(0,t.isVNode)(n)){r(n);continue}let e=n;if(e.type!==t.Text&&e.type!==t.Comment){if(typeof e.type==`object`||typeof e.type==`function`){let t=e.type.name??e.type.__name;throw Error(`Components are not allowed inside \`<T>...</T>\` components.${t?` The component found inside the T component is ${t}.`:``}`)}r(e.children)}}}var i=(0,t.defineComponent)({name:`T`,props:{lang:String,formality:String},setup(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@magic-translate/core"),t=require("vue");function n(e){let t=e.cloneNode(!0),n=[],r,i=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT);for(let e=i.nextNode();e;e=i.nextNode()){if(e.nodeType===Node.COMMENT_NODE){n.push(e);continue}let t=e;for(let{name:e}of Array.from(t.attributes))e.startsWith(`data-v-`)&&(r=e,t.removeAttribute(e))}n.forEach(e=>e.parentNode?.removeChild(e));let[,a,o,s]=/^(\s*)([\s\S]*?)(\s*)$/.exec(t.innerHTML);return{html:o,leadingWhitespace:a,trailingWhitespace:s,scopeAttribute:r}}function r(e){if(Array.isArray(e))for(let n of e){if(!(0,t.isVNode)(n)){r(n);continue}let e=n;if(e.type!==t.Text&&e.type!==t.Comment){if(typeof e.type==`object`||typeof e.type==`function`){let t=e.type.name??e.type.__name;throw Error(`Components are not allowed inside \`<T>...</T>\` components.${t?` The component found inside the T component is ${t}.`:``}`)}r(e.children)}}}var i=(0,t.defineComponent)({name:`T`,props:{lang:String,formality:String,v:Object},setup(i,{slots:a}){let o=c(),s=(0,t.ref)(),l=(0,t.ref)(),u=(0,t.shallowRef)(null),d=(0,t.ref)(!1),f=0,p,m,h=(t,n,r=[])=>{let i=l.value;i&&(i.innerHTML=`${t.leadingWhitespace}${n}${t.trailingWhitespace}`,r.length>0&&(0,e.fillElement)(i,r),t.scopeAttribute&&i.querySelectorAll(`*`).forEach(e=>e.setAttribute(t.scopeAttribute,``)),d.value=!0)};return(0,t.onMounted)(()=>{let e=s.value,t=()=>{let t=n(e),r=u.value;(t.html!==r?.html||t.leadingWhitespace!==r.leadingWhitespace||t.trailingWhitespace!==r.trailingWhitespace)&&(u.value=t)};t(),m=new MutationObserver(t),m.observe(e,{subtree:!0,childList:!0,characterData:!0,attributes:!0})}),(0,t.onBeforeUnmount)(()=>m?.disconnect()),(0,t.watch)([u,()=>i.lang,()=>i.formality,()=>i.v,o.language],([t,n,r,i])=>{if(!t?.html){d.value=!1;return}let{template:a,values:s,unused:c}=i?(0,e.toHtmlTemplate)(t.html,i):{template:t.html,values:[],unused:[]};a!==p&&(p=a,(0,e.warnAboutUnused)(c)),i&&!d.value&&h(t,a,s);let l=++f;o.translate(a,{lang:n,formality:r}).then(e=>{l===f&&h(t,e,s)})}),()=>{let e=a.default?.();return r(e),(0,t.h)(`span`,{style:`display: contents`},[(0,t.h)(`span`,{ref:s,style:{display:d.value||i.v?`none`:`contents`}},e),(0,t.h)(`span`,{ref:l,style:{display:d.value?`contents`:`none`}})])}}}),a=i,o=Symbol(`magic-translate`);function s(n){if(!n.apiKey)throw Error(`Missing API key. Please get one at ${e.libraryWebsite}`);let{debug:r,apiKey:a,sourceLanguage:s,_apiUrl:c,formality:l}=n,u=n._loaderFactory??e.defaultLoaderFactory,d=(0,t.ref)(n.language),f=(0,t.computed)(()=>new e.DataLoader(u({debug:!!r,apiKey:a,lang:d.value,sourceLang:s,apiUrl:c,formality:l}),{cacheKeyFn:(0,e.cacheKeyFn)(a,s,d.value)})),p={language:(0,t.readonly)(d),apiKey:a,sourceLanguage:s,dataLoader:f,setLanguage(e){d.value=e},translate(t,{v:n,...r}={}){if(!n)return f.value.load({text:t,...r});let{template:i,values:a,unused:o}=(0,e.toTemplate)(t,n);return(0,e.warnAboutUnused)(o),f.value.load({text:i,...r}).then(t=>(0,e.fillPlaceholders)(t,a))},cacheKey(t,n={}){return(0,e.cacheKeyFn)(a,s,d.value)({text:t,...n})},install(e){e.provide(o,p),e.component(`T`,i)}};return p}function c(){let e=(0,t.inject)(o,null);if(e===null)throw Error(`useMagicTranslate must be used in an app that installed createMagicTranslate()`);return e}function l(){let{setLanguage:e,language:t}=c();return{setLanguage:e,currentLanguage:t}}function u(){let n=c(),r=(0,t.reactive)(new Map),i=new Set;return(t,{v:a,...o}={})=>{let{template:s,values:c,unused:l}=a?(0,e.toTemplate)(t,a):{template:t,values:[],unused:[]};if(!t||typeof window>`u`)return(0,e.fillPlaceholders)(s,c);let u=n.cacheKey(s,o),d=r.get(u);return d===void 0&&!i.has(u)&&((0,e.warnAboutUnused)(l),i.add(u),n.translate(s,o).then(e=>{r.set(u,e),i.delete(u)})),(0,e.fillPlaceholders)(d??s,c)}}var d=u;exports.MAGIC_TRANSLATE=o,exports.T=a,exports.Translate=i,exports.createMagicTranslate=s,exports.useLanguageSwitcher=l,exports.useMagicTranslate=c,exports.useT=d,exports.useTranslate=u,Object.keys(e).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["isVNode","Text","Comment","defineComponent","ref","shallowRef","h","libraryWebsite","defaultLoaderFactory","ref","computed","DataLoader","cacheKeyFn","readonly","inject","reactive","_magic_translate_core","vue","readSource","clone","element","comments","scopeAttribute","walker","node","el","name","comment","leadingWhitespace","html","trailingWhitespace","assertNoComponents","children","child","vnode","Translate","magicTranslate","useMagicTranslate","sourceRef","targetRef","source","translated","lastRequestId","observer","render","target","translation","read","next","current","props","lang","formality","requestId","slots","T","MAGIC_TRANSLATE","createMagicTranslate","config","loaderFactory","language","dataLoader","debug","sourceLanguage","_apiUrl","apiKey","params","app","useLanguageSwitcher","useTranslate","translations","pending","text","key","useT","k"],"sources":["../src/translate.ts","../src/magic-translate.ts","../src/use-translate.ts"],"sourcesContent":["import {\n Comment,\n defineComponent,\n h,\n isVNode,\n onBeforeUnmount,\n onMounted,\n PropType,\n ref,\n shallowRef,\n Text,\n VNode,\n watch,\n} from 'vue';\nimport { TranslateOptions } from '@magic-translate/core';\nimport { Languages, useMagicTranslate } from './magic-translate';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `data-v-*` attribute of the parent's scoped styles, so they still\n // apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the slot content into the HTML string that gets translated, without\n * the comments and attributes Vue adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('data-v-')) {\n scopeAttribute = name;\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\nfunction assertNoComponents(children: unknown) {\n if (!Array.isArray(children)) {\n return;\n }\n\n for (const child of children) {\n if (!isVNode(child)) {\n assertNoComponents(child);\n continue;\n }\n\n const vnode = child as VNode;\n if (vnode.type === Text || vnode.type === Comment) {\n continue;\n }\n if (typeof vnode.type === 'object' || typeof vnode.type === 'function') {\n const name =\n (vnode.type as { name?: string; __name?: string }).name ??\n (vnode.type as { __name?: string }).__name;\n throw new Error(\n `Components are not allowed inside \\`<T>...</T>\\` components.${\n name ? ` The component found inside the T component is ${name}.` : ''\n }`\n );\n }\n // elements and fragments\n assertNoComponents(vnode.children);\n }\n}\n\n/**\n * Translates its content, which can be text and plain HTML elements. The\n * original content is shown until the translation has loaded.\n */\nexport const Translate = defineComponent({\n name: 'T',\n props: {\n lang: String as PropType<Languages>,\n formality: String as PropType<TranslateOptions['formality']>,\n },\n setup(props, { slots }) {\n const magicTranslate = useMagicTranslate();\n const sourceRef = ref<HTMLElement>();\n const targetRef = ref<HTMLElement>();\n const source = shallowRef<Source | null>(null);\n const translated = ref(false);\n let lastRequestId = 0;\n let observer: MutationObserver | undefined;\n\n const render = (source: Source, translation: string) => {\n const target = targetRef.value;\n if (!target) {\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${translation}${source.trailingWhitespace}`;\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n translated.value = true;\n };\n\n onMounted(() => {\n const element = sourceRef.value!;\n const read = () => {\n const next = readSource(element);\n const current = source.value;\n if (\n next.html !== current?.html ||\n next.leadingWhitespace !== current.leadingWhitespace ||\n next.trailingWhitespace !== current.trailingWhitespace\n ) {\n source.value = next;\n }\n };\n read();\n\n // Picks up changes to the slot content, such as interpolations.\n observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n });\n\n onBeforeUnmount(() => observer?.disconnect());\n\n watch(\n [\n source,\n () => props.lang,\n () => props.formality,\n // a language switch has to re-translate the content\n magicTranslate.language,\n ],\n ([source, lang, formality]) => {\n if (!source?.html) {\n translated.value = false;\n return;\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++lastRequestId;\n magicTranslate\n .translate(source.html, { lang, formality })\n .then((translation) => {\n if (requestId === lastRequestId) {\n render(source, translation);\n }\n });\n }\n );\n\n return () => {\n const children = slots.default?.();\n assertNoComponents(children);\n\n return h('span', { style: 'display: contents' }, [\n h(\n 'span',\n {\n ref: sourceRef,\n style: { display: translated.value ? 'none' : 'contents' },\n },\n children\n ),\n h('span', {\n ref: targetRef,\n style: { display: translated.value ? 'contents' : 'none' },\n }),\n ]);\n };\n },\n});\n\nexport const T = Translate;\n\n// The plugin registers <T> globally, so templates type-check without importing it.\ndeclare module 'vue' {\n export interface GlobalComponents {\n T: typeof Translate;\n }\n}\n","import {\n App,\n computed,\n ComputedRef,\n inject,\n InjectionKey,\n readonly,\n ref,\n Ref,\n} from 'vue';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n Language,\n libraryWebsite,\n MagicTranslateConfig,\n} from '@magic-translate/core';\nimport { Translate } from './translate';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport type TranslateParams = Omit<DataLoaderLoadParams, 'text'>;\n\nexport interface MagicTranslate {\n /**\n * The language content is currently translated to.\n */\n readonly language: Readonly<Ref<Languages>>;\n readonly apiKey: string;\n readonly sourceLanguage?: Languages;\n /** @ignore */\n readonly dataLoader: ComputedRef<\n DataLoader<DataLoaderLoadParams, string, string>\n >;\n setLanguage(language: Languages): void;\n translate(text: string, params?: TranslateParams): Promise<string>;\n /** @ignore */\n cacheKey(text: string, params?: TranslateParams): string;\n install(app: App): void;\n}\n\nexport const MAGIC_TRANSLATE: InjectionKey<MagicTranslate> =\n Symbol('magic-translate');\n\n/**\n * Sets up Magic Translate. Install the result as a plugin with `app.use()`.\n * It can also be used directly, outside of components.\n */\nexport function createMagicTranslate(\n config: MagicTranslateConfig\n): MagicTranslate {\n if (!config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n\n const { debug, apiKey, sourceLanguage, _apiUrl, formality } = config;\n const loaderFactory = config._loaderFactory ?? defaultLoaderFactory;\n const language = ref<Languages>(config.language);\n\n const dataLoader = computed(\n () =>\n new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n lang: language.value,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language.value) }\n )\n );\n\n const magicTranslate: MagicTranslate = {\n language: readonly(language),\n apiKey,\n sourceLanguage,\n dataLoader,\n setLanguage(lang) {\n language.value = lang;\n },\n translate(text, params = {}) {\n return dataLoader.value.load({ text, ...params });\n },\n cacheKey(text, params = {}) {\n return cacheKeyFn(\n apiKey,\n sourceLanguage,\n language.value\n )({\n text,\n ...params,\n });\n },\n install(app) {\n app.provide(MAGIC_TRANSLATE, magicTranslate);\n app.component('T', Translate);\n },\n };\n\n return magicTranslate;\n}\n\nexport function useMagicTranslate(): MagicTranslate {\n const magicTranslate = inject(MAGIC_TRANSLATE, null);\n\n if (magicTranslate === null) {\n throw new Error(\n 'useMagicTranslate must be used in an app that installed createMagicTranslate()'\n );\n }\n\n return magicTranslate;\n}\n\nexport function useLanguageSwitcher(): {\n setLanguage: (language: Languages) => void;\n currentLanguage: Readonly<Ref<Languages>>;\n} {\n const { setLanguage, language } = useMagicTranslate();\n\n return { setLanguage, currentLanguage: language };\n}\n","import { reactive } from 'vue';\nimport { TranslateParams, useMagicTranslate } from './magic-translate';\n\n/**\n * Returns a function that translates plain strings, e.g. for attributes:\n * `<input :placeholder=\"t('Search')\" />`. It returns the original text until\n * the translation has loaded, and the component re-renders once it has.\n */\nexport function useTranslate(): (\n text: string,\n params?: TranslateParams\n) => string {\n const magicTranslate = useMagicTranslate();\n const translations = reactive(new Map<string, string>());\n const pending = new Set<string>();\n\n return (text, params = {}) => {\n // Translations are only loaded in the browser.\n if (!text || typeof window === 'undefined') {\n return text;\n }\n\n // reads the current language, so a language switch re-renders\n const key = magicTranslate.cacheKey(text, params);\n const translation = translations.get(key);\n\n if (translation === undefined && !pending.has(key)) {\n pending.add(key);\n magicTranslate.translate(text, params).then((translated) => {\n translations.set(key, translated);\n pending.delete(key);\n });\n }\n\n return translation ?? text;\n };\n}\n\nexport const useT = useTranslate;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["isVNode","Text","Comment","defineComponent","ref","shallowRef","toHtmlTemplate","h","libraryWebsite","defaultLoaderFactory","ref","computed","DataLoader","cacheKeyFn","readonly","toTemplate","fillPlaceholders","inject","reactive","toTemplate","fillPlaceholders","_magic_translate_core","vue","readSource","clone","element","comments","scopeAttribute","walker","node","el","name","comment","leadingWhitespace","html","trailingWhitespace","assertNoComponents","children","child","vnode","Translate","magicTranslate","useMagicTranslate","sourceRef","targetRef","source","translated","lastRequestId","lastTemplate","observer","render","target","translation","values","read","next","current","props","lang","formality","variables","template","unused","requestId","slots","T","MAGIC_TRANSLATE","createMagicTranslate","config","loaderFactory","language","dataLoader","debug","sourceLanguage","_apiUrl","apiKey","params","v","text","app","useLanguageSwitcher","useTranslate","translations","pending","key","useT","k"],"sources":["../src/translate.ts","../src/magic-translate.ts","../src/use-translate.ts"],"sourcesContent":["import {\n Comment,\n defineComponent,\n h,\n isVNode,\n onBeforeUnmount,\n onMounted,\n PropType,\n ref,\n shallowRef,\n Text,\n VNode,\n watch,\n} from 'vue';\nimport {\n fillElement,\n toHtmlTemplate,\n TranslateOptions,\n Variables,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { Languages, useMagicTranslate } from './magic-translate';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `data-v-*` attribute of the parent's scoped styles, so they still\n // apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the slot content into the HTML string that gets translated, without\n * the comments and attributes Vue adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('data-v-')) {\n scopeAttribute = name;\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\nfunction assertNoComponents(children: unknown) {\n if (!Array.isArray(children)) {\n return;\n }\n\n for (const child of children) {\n if (!isVNode(child)) {\n assertNoComponents(child);\n continue;\n }\n\n const vnode = child as VNode;\n if (vnode.type === Text || vnode.type === Comment) {\n continue;\n }\n if (typeof vnode.type === 'object' || typeof vnode.type === 'function') {\n const name =\n (vnode.type as { name?: string; __name?: string }).name ??\n (vnode.type as { __name?: string }).__name;\n throw new Error(\n `Components are not allowed inside \\`<T>...</T>\\` components.${\n name ? ` The component found inside the T component is ${name}.` : ''\n }`\n );\n }\n // elements and fragments\n assertNoComponents(vnode.children);\n }\n}\n\n/**\n * Translates its content, which can be text and plain HTML elements. The\n * original content is shown until the translation has loaded.\n */\nexport const Translate = defineComponent({\n name: 'T',\n props: {\n lang: String as PropType<Languages>,\n formality: String as PropType<TranslateOptions['formality']>,\n /**\n * Values that are not translated, referenced as `$name` in the content.\n * The text is translated once as a template and cached, whatever the\n * values are.\n */\n v: Object as PropType<Variables>,\n },\n setup(props, { slots }) {\n const magicTranslate = useMagicTranslate();\n const sourceRef = ref<HTMLElement>();\n const targetRef = ref<HTMLElement>();\n const source = shallowRef<Source | null>(null);\n const translated = ref(false);\n let lastRequestId = 0;\n let lastTemplate: string | undefined;\n let observer: MutationObserver | undefined;\n\n const render = (\n source: Source,\n translation: string,\n values: readonly string[] = []\n ) => {\n const target = targetRef.value;\n if (!target) {\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${translation}${source.trailingWhitespace}`;\n if (values.length > 0) {\n fillElement(target, values);\n }\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n translated.value = true;\n };\n\n onMounted(() => {\n const element = sourceRef.value!;\n const read = () => {\n const next = readSource(element);\n const current = source.value;\n if (\n next.html !== current?.html ||\n next.leadingWhitespace !== current.leadingWhitespace ||\n next.trailingWhitespace !== current.trailingWhitespace\n ) {\n source.value = next;\n }\n };\n read();\n\n // Picks up changes to the slot content, such as interpolations.\n observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n });\n\n onBeforeUnmount(() => observer?.disconnect());\n\n watch(\n [\n source,\n () => props.lang,\n () => props.formality,\n () => props.v,\n // a language switch has to re-translate the content\n magicTranslate.language,\n ],\n ([source, lang, formality, variables]) => {\n if (!source?.html) {\n translated.value = false;\n return;\n }\n\n const { template, values, unused } = variables\n ? toHtmlTemplate(source.html, variables)\n : { template: source.html, values: [], unused: [] };\n if (template !== lastTemplate) {\n lastTemplate = template;\n warnAboutUnused(unused);\n }\n if (variables && !translated.value) {\n render(source, template, values);\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++lastRequestId;\n magicTranslate\n .translate(template, { lang, formality })\n .then((translation) => {\n if (requestId === lastRequestId) {\n render(source, translation, values);\n }\n });\n }\n );\n\n return () => {\n const children = slots.default?.();\n assertNoComponents(children);\n\n return h('span', { style: 'display: contents' }, [\n h(\n 'span',\n {\n ref: sourceRef,\n // With variables, the source would show `$name`, so it stays\n // hidden and the text is shown with the values filled in until\n // the translation arrives.\n style: {\n display: translated.value || props.v ? 'none' : 'contents',\n },\n },\n children\n ),\n h('span', {\n ref: targetRef,\n style: { display: translated.value ? 'contents' : 'none' },\n }),\n ]);\n };\n },\n});\n\nexport const T = Translate;\n\n// The plugin registers <T> globally, so templates type-check without importing it.\ndeclare module 'vue' {\n export interface GlobalComponents {\n T: typeof Translate;\n }\n}\n","import {\n App,\n computed,\n ComputedRef,\n inject,\n InjectionKey,\n readonly,\n ref,\n Ref,\n} from 'vue';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n fillPlaceholders,\n Language,\n libraryWebsite,\n MagicTranslateConfig,\n toTemplate,\n Variables,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { Translate } from './translate';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport interface TranslateParams extends Omit<DataLoaderLoadParams, 'text'> {\n /**\n * Values that are not translated, referenced as `$name` in the text. The text\n * is translated once as a template and cached, whatever the values are.\n */\n v?: Variables;\n}\n\nexport interface MagicTranslate {\n /**\n * The language content is currently translated to.\n */\n readonly language: Readonly<Ref<Languages>>;\n readonly apiKey: string;\n readonly sourceLanguage?: Languages;\n /** @ignore */\n readonly dataLoader: ComputedRef<\n DataLoader<DataLoaderLoadParams, string, string>\n >;\n setLanguage(language: Languages): void;\n translate(text: string, params?: TranslateParams): Promise<string>;\n /** @ignore */\n cacheKey(text: string, params?: Omit<TranslateParams, 'v'>): string;\n install(app: App): void;\n}\n\nexport const MAGIC_TRANSLATE: InjectionKey<MagicTranslate> =\n Symbol('magic-translate');\n\n/**\n * Sets up Magic Translate. Install the result as a plugin with `app.use()`.\n * It can also be used directly, outside of components.\n */\nexport function createMagicTranslate(\n config: MagicTranslateConfig\n): MagicTranslate {\n if (!config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n\n const { debug, apiKey, sourceLanguage, _apiUrl, formality } = config;\n const loaderFactory = config._loaderFactory ?? defaultLoaderFactory;\n const language = ref<Languages>(config.language);\n\n const dataLoader = computed(\n () =>\n new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n lang: language.value,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language.value) }\n )\n );\n\n const magicTranslate: MagicTranslate = {\n language: readonly(language),\n apiKey,\n sourceLanguage,\n dataLoader,\n setLanguage(lang) {\n language.value = lang;\n },\n translate(text, { v, ...params } = {}) {\n if (!v) {\n return dataLoader.value.load({ text, ...params });\n }\n const { template, values, unused } = toTemplate(text, v);\n warnAboutUnused(unused);\n return dataLoader.value\n .load({ text: template, ...params })\n .then((translation) => fillPlaceholders(translation, values));\n },\n cacheKey(text, params = {}) {\n return cacheKeyFn(\n apiKey,\n sourceLanguage,\n language.value\n )({\n text,\n ...params,\n });\n },\n install(app) {\n app.provide(MAGIC_TRANSLATE, magicTranslate);\n app.component('T', Translate);\n },\n };\n\n return magicTranslate;\n}\n\nexport function useMagicTranslate(): MagicTranslate {\n const magicTranslate = inject(MAGIC_TRANSLATE, null);\n\n if (magicTranslate === null) {\n throw new Error(\n 'useMagicTranslate must be used in an app that installed createMagicTranslate()'\n );\n }\n\n return magicTranslate;\n}\n\nexport function useLanguageSwitcher(): {\n setLanguage: (language: Languages) => void;\n currentLanguage: Readonly<Ref<Languages>>;\n} {\n const { setLanguage, language } = useMagicTranslate();\n\n return { setLanguage, currentLanguage: language };\n}\n","import { reactive } from 'vue';\nimport {\n fillPlaceholders,\n toTemplate,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { TranslateParams, useMagicTranslate } from './magic-translate';\n\n/**\n * Returns a function that translates plain strings, e.g. for attributes:\n * `<input :placeholder=\"t('Search')\" />`. It returns the original text until\n * the translation has loaded, and the component re-renders once it has.\n *\n * Values that should not be translated go in `v` and are referenced as `$name`:\n * `t('Search $count products', { v: { count } })`.\n */\nexport function useTranslate(): (\n text: string,\n params?: TranslateParams\n) => string {\n const magicTranslate = useMagicTranslate();\n const translations = reactive(new Map<string, string>());\n const pending = new Set<string>();\n\n return (text, { v, ...params } = {}) => {\n // Only the template is translated, so new values reuse the translation.\n const { template, values, unused } = v\n ? toTemplate(text, v)\n : { template: text, values: [], unused: [] };\n\n // Translations are only loaded in the browser.\n if (!text || typeof window === 'undefined') {\n return fillPlaceholders(template, values);\n }\n\n // reads the current language, so a language switch re-renders\n const key = magicTranslate.cacheKey(template, params);\n const translation = translations.get(key);\n\n if (translation === undefined && !pending.has(key)) {\n warnAboutUnused(unused);\n pending.add(key);\n magicTranslate.translate(template, params).then((translated) => {\n translations.set(key, translated);\n pending.delete(key);\n });\n }\n\n return fillPlaceholders(translation ?? template, values);\n };\n}\n\nexport const useT = useTranslate;\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DataLoader as e, cacheKeyFn as t, defaultLoaderFactory as n,
|
|
2
|
-
import { Comment as
|
|
1
|
+
import { DataLoader as e, cacheKeyFn as t, defaultLoaderFactory as n, fillElement as r, fillPlaceholders as i, libraryWebsite as a, toHtmlTemplate as o, toTemplate as s, warnAboutUnused as c } from "@magic-translate/core";
|
|
2
|
+
import { Comment as l, Text as u, computed as d, defineComponent as f, h as p, inject as m, isVNode as h, onBeforeUnmount as g, onMounted as _, reactive as v, readonly as y, ref as b, shallowRef as x, watch as S } from "vue";
|
|
3
3
|
export * from "@magic-translate/core";
|
|
4
4
|
// src/translate.ts
|
|
5
|
-
function
|
|
5
|
+
function C(e) {
|
|
6
6
|
let t = e.cloneNode(!0), n = [], r, i = document.createTreeWalker(t, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);
|
|
7
7
|
for (let e = i.nextNode(); e; e = i.nextNode()) {
|
|
8
8
|
if (e.nodeType === Node.COMMENT_NODE) {
|
|
@@ -21,132 +21,150 @@ function v(e) {
|
|
|
21
21
|
scopeAttribute: r
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function w(e) {
|
|
25
25
|
if (Array.isArray(e)) for (let t of e) {
|
|
26
|
-
if (!
|
|
27
|
-
|
|
26
|
+
if (!h(t)) {
|
|
27
|
+
w(t);
|
|
28
28
|
continue;
|
|
29
29
|
}
|
|
30
30
|
let e = t;
|
|
31
|
-
if (e.type !==
|
|
31
|
+
if (e.type !== u && e.type !== l) {
|
|
32
32
|
if (typeof e.type == "object" || typeof e.type == "function") {
|
|
33
33
|
let t = e.type.name ?? e.type.__name;
|
|
34
34
|
throw Error(`Components are not allowed inside \`<T>...</T>\` components.${t ? ` The component found inside the T component is ${t}.` : ""}`);
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
w(e.children);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
var
|
|
40
|
+
var T = f({
|
|
41
41
|
name: "T",
|
|
42
42
|
props: {
|
|
43
43
|
lang: String,
|
|
44
|
-
formality: String
|
|
44
|
+
formality: String,
|
|
45
|
+
v: Object
|
|
45
46
|
},
|
|
46
47
|
setup(e, { slots: t }) {
|
|
47
|
-
let n =
|
|
48
|
-
let
|
|
49
|
-
|
|
48
|
+
let n = k(), i = b(), a = b(), s = x(null), l = b(!1), u = 0, d, f, m = (e, t, n = []) => {
|
|
49
|
+
let i = a.value;
|
|
50
|
+
i && (i.innerHTML = `${e.leadingWhitespace}${t}${e.trailingWhitespace}`, n.length > 0 && r(i, n), e.scopeAttribute && i.querySelectorAll("*").forEach((t) => t.setAttribute(e.scopeAttribute, "")), l.value = !0);
|
|
50
51
|
};
|
|
51
|
-
return
|
|
52
|
-
let e =
|
|
53
|
-
let t =
|
|
54
|
-
(t.html !== n?.html || t.leadingWhitespace !== n.leadingWhitespace || t.trailingWhitespace !== n.trailingWhitespace) && (
|
|
52
|
+
return _(() => {
|
|
53
|
+
let e = i.value, t = () => {
|
|
54
|
+
let t = C(e), n = s.value;
|
|
55
|
+
(t.html !== n?.html || t.leadingWhitespace !== n.leadingWhitespace || t.trailingWhitespace !== n.trailingWhitespace) && (s.value = t);
|
|
55
56
|
};
|
|
56
|
-
t(),
|
|
57
|
+
t(), f = new MutationObserver(t), f.observe(e, {
|
|
57
58
|
subtree: !0,
|
|
58
59
|
childList: !0,
|
|
59
60
|
characterData: !0,
|
|
60
61
|
attributes: !0
|
|
61
62
|
});
|
|
62
|
-
}),
|
|
63
|
-
|
|
63
|
+
}), g(() => f?.disconnect()), S([
|
|
64
|
+
s,
|
|
64
65
|
() => e.lang,
|
|
65
66
|
() => e.formality,
|
|
67
|
+
() => e.v,
|
|
66
68
|
n.language
|
|
67
|
-
], ([e, t, r]) => {
|
|
69
|
+
], ([e, t, r, i]) => {
|
|
68
70
|
if (!e?.html) {
|
|
69
|
-
|
|
71
|
+
l.value = !1;
|
|
70
72
|
return;
|
|
71
73
|
}
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
+
let { template: a, values: s, unused: f } = i ? o(e.html, i) : {
|
|
75
|
+
template: e.html,
|
|
76
|
+
values: [],
|
|
77
|
+
unused: []
|
|
78
|
+
};
|
|
79
|
+
a !== d && (d = a, c(f)), i && !l.value && m(e, a, s);
|
|
80
|
+
let p = ++u;
|
|
81
|
+
n.translate(a, {
|
|
74
82
|
lang: t,
|
|
75
83
|
formality: r
|
|
76
84
|
}).then((t) => {
|
|
77
|
-
|
|
85
|
+
p === u && m(e, t, s);
|
|
78
86
|
});
|
|
79
87
|
}), () => {
|
|
80
|
-
let
|
|
81
|
-
return
|
|
82
|
-
ref: r,
|
|
83
|
-
style: { display: o.value ? "none" : "contents" }
|
|
84
|
-
}, e), c("span", {
|
|
88
|
+
let n = t.default?.();
|
|
89
|
+
return w(n), p("span", { style: "display: contents" }, [p("span", {
|
|
85
90
|
ref: i,
|
|
86
|
-
style: { display:
|
|
91
|
+
style: { display: l.value || e.v ? "none" : "contents" }
|
|
92
|
+
}, n), p("span", {
|
|
93
|
+
ref: a,
|
|
94
|
+
style: { display: l.value ? "contents" : "none" }
|
|
87
95
|
})]);
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
|
-
}),
|
|
91
|
-
function
|
|
92
|
-
if (!
|
|
93
|
-
let { debug:
|
|
94
|
-
debug: !!
|
|
95
|
-
apiKey:
|
|
96
|
-
lang:
|
|
97
|
-
sourceLang:
|
|
98
|
-
apiUrl:
|
|
99
|
-
formality:
|
|
100
|
-
}), { cacheKeyFn: t(
|
|
101
|
-
language:
|
|
102
|
-
apiKey:
|
|
103
|
-
sourceLanguage:
|
|
104
|
-
dataLoader:
|
|
98
|
+
}), E = T, D = Symbol("magic-translate");
|
|
99
|
+
function O(r) {
|
|
100
|
+
if (!r.apiKey) throw Error(`Missing API key. Please get one at ${a}`);
|
|
101
|
+
let { debug: o, apiKey: l, sourceLanguage: u, _apiUrl: f, formality: p } = r, m = r._loaderFactory ?? n, h = b(r.language), g = d(() => new e(m({
|
|
102
|
+
debug: !!o,
|
|
103
|
+
apiKey: l,
|
|
104
|
+
lang: h.value,
|
|
105
|
+
sourceLang: u,
|
|
106
|
+
apiUrl: f,
|
|
107
|
+
formality: p
|
|
108
|
+
}), { cacheKeyFn: t(l, u, h.value) })), _ = {
|
|
109
|
+
language: y(h),
|
|
110
|
+
apiKey: l,
|
|
111
|
+
sourceLanguage: u,
|
|
112
|
+
dataLoader: g,
|
|
105
113
|
setLanguage(e) {
|
|
106
|
-
|
|
114
|
+
h.value = e;
|
|
107
115
|
},
|
|
108
|
-
translate(e, t = {}) {
|
|
109
|
-
return
|
|
116
|
+
translate(e, { v: t, ...n } = {}) {
|
|
117
|
+
if (!t) return g.value.load({
|
|
110
118
|
text: e,
|
|
111
|
-
...
|
|
119
|
+
...n
|
|
112
120
|
});
|
|
121
|
+
let { template: r, values: a, unused: o } = s(e, t);
|
|
122
|
+
return c(o), g.value.load({
|
|
123
|
+
text: r,
|
|
124
|
+
...n
|
|
125
|
+
}).then((e) => i(e, a));
|
|
113
126
|
},
|
|
114
127
|
cacheKey(e, n = {}) {
|
|
115
|
-
return t(
|
|
128
|
+
return t(l, u, h.value)({
|
|
116
129
|
text: e,
|
|
117
130
|
...n
|
|
118
131
|
});
|
|
119
132
|
},
|
|
120
133
|
install(e) {
|
|
121
|
-
e.provide(
|
|
134
|
+
e.provide(D, _), e.component("T", T);
|
|
122
135
|
}
|
|
123
136
|
};
|
|
124
|
-
return
|
|
137
|
+
return _;
|
|
125
138
|
}
|
|
126
|
-
function
|
|
127
|
-
let e =
|
|
139
|
+
function k() {
|
|
140
|
+
let e = m(D, null);
|
|
128
141
|
if (e === null) throw Error("useMagicTranslate must be used in an app that installed createMagicTranslate()");
|
|
129
142
|
return e;
|
|
130
143
|
}
|
|
131
|
-
function
|
|
132
|
-
let { setLanguage: e, language: t } =
|
|
144
|
+
function A() {
|
|
145
|
+
let { setLanguage: e, language: t } = k();
|
|
133
146
|
return {
|
|
134
147
|
setLanguage: e,
|
|
135
148
|
currentLanguage: t
|
|
136
149
|
};
|
|
137
150
|
}
|
|
138
151
|
// src/use-translate.ts
|
|
139
|
-
function
|
|
140
|
-
let e =
|
|
141
|
-
return (r,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
152
|
+
function j() {
|
|
153
|
+
let e = k(), t = v(/* @__PURE__ */ new Map()), n = /* @__PURE__ */ new Set();
|
|
154
|
+
return (r, { v: a, ...o } = {}) => {
|
|
155
|
+
let { template: l, values: u, unused: d } = a ? s(r, a) : {
|
|
156
|
+
template: r,
|
|
157
|
+
values: [],
|
|
158
|
+
unused: []
|
|
159
|
+
};
|
|
160
|
+
if (!r || typeof window > "u") return i(l, u);
|
|
161
|
+
let f = e.cacheKey(l, o), p = t.get(f);
|
|
162
|
+
return p === void 0 && !n.has(f) && (c(d), n.add(f), e.translate(l, o).then((e) => {
|
|
163
|
+
t.set(f, e), n.delete(f);
|
|
164
|
+
})), i(p ?? l, u);
|
|
147
165
|
};
|
|
148
166
|
}
|
|
149
|
-
var
|
|
150
|
-
export {
|
|
167
|
+
var M = j;
|
|
168
|
+
export { D as MAGIC_TRANSLATE, E as T, T as Translate, O as createMagicTranslate, A as useLanguageSwitcher, k as useMagicTranslate, M as useT, j as useTranslate };
|
|
151
169
|
|
|
152
170
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["readSource","clone","element","comments","scopeAttribute","walker","node","el","name","comment","leadingWhitespace","html","trailingWhitespace","assertNoComponents","children","child","isVNode","vnode","Text","Comment","Translate","defineComponent","magicTranslate","useMagicTranslate","sourceRef","ref","targetRef","source","shallowRef","translated","lastRequestId","observer","render","target","translation","onMounted","read","next","current","onBeforeUnmount","watch","props","lang","formality","requestId","slots","h","T","MAGIC_TRANSLATE","createMagicTranslate","config","libraryWebsite","loaderFactory","defaultLoaderFactory","language","dataLoader","computed","DataLoader","debug","sourceLanguage","_apiUrl","cacheKeyFn","apiKey","readonly","params","app","inject","useLanguageSwitcher","useTranslate","translations","reactive","pending","text","key","useT"],"sources":["../src/translate.ts","../src/magic-translate.ts","../src/use-translate.ts"],"sourcesContent":["import {\n Comment,\n defineComponent,\n h,\n isVNode,\n onBeforeUnmount,\n onMounted,\n PropType,\n ref,\n shallowRef,\n Text,\n VNode,\n watch,\n} from 'vue';\nimport { TranslateOptions } from '@magic-translate/core';\nimport { Languages, useMagicTranslate } from './magic-translate';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `data-v-*` attribute of the parent's scoped styles, so they still\n // apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the slot content into the HTML string that gets translated, without\n * the comments and attributes Vue adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('data-v-')) {\n scopeAttribute = name;\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\nfunction assertNoComponents(children: unknown) {\n if (!Array.isArray(children)) {\n return;\n }\n\n for (const child of children) {\n if (!isVNode(child)) {\n assertNoComponents(child);\n continue;\n }\n\n const vnode = child as VNode;\n if (vnode.type === Text || vnode.type === Comment) {\n continue;\n }\n if (typeof vnode.type === 'object' || typeof vnode.type === 'function') {\n const name =\n (vnode.type as { name?: string; __name?: string }).name ??\n (vnode.type as { __name?: string }).__name;\n throw new Error(\n `Components are not allowed inside \\`<T>...</T>\\` components.${\n name ? ` The component found inside the T component is ${name}.` : ''\n }`\n );\n }\n // elements and fragments\n assertNoComponents(vnode.children);\n }\n}\n\n/**\n * Translates its content, which can be text and plain HTML elements. The\n * original content is shown until the translation has loaded.\n */\nexport const Translate = defineComponent({\n name: 'T',\n props: {\n lang: String as PropType<Languages>,\n formality: String as PropType<TranslateOptions['formality']>,\n },\n setup(props, { slots }) {\n const magicTranslate = useMagicTranslate();\n const sourceRef = ref<HTMLElement>();\n const targetRef = ref<HTMLElement>();\n const source = shallowRef<Source | null>(null);\n const translated = ref(false);\n let lastRequestId = 0;\n let observer: MutationObserver | undefined;\n\n const render = (source: Source, translation: string) => {\n const target = targetRef.value;\n if (!target) {\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${translation}${source.trailingWhitespace}`;\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n translated.value = true;\n };\n\n onMounted(() => {\n const element = sourceRef.value!;\n const read = () => {\n const next = readSource(element);\n const current = source.value;\n if (\n next.html !== current?.html ||\n next.leadingWhitespace !== current.leadingWhitespace ||\n next.trailingWhitespace !== current.trailingWhitespace\n ) {\n source.value = next;\n }\n };\n read();\n\n // Picks up changes to the slot content, such as interpolations.\n observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n });\n\n onBeforeUnmount(() => observer?.disconnect());\n\n watch(\n [\n source,\n () => props.lang,\n () => props.formality,\n // a language switch has to re-translate the content\n magicTranslate.language,\n ],\n ([source, lang, formality]) => {\n if (!source?.html) {\n translated.value = false;\n return;\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++lastRequestId;\n magicTranslate\n .translate(source.html, { lang, formality })\n .then((translation) => {\n if (requestId === lastRequestId) {\n render(source, translation);\n }\n });\n }\n );\n\n return () => {\n const children = slots.default?.();\n assertNoComponents(children);\n\n return h('span', { style: 'display: contents' }, [\n h(\n 'span',\n {\n ref: sourceRef,\n style: { display: translated.value ? 'none' : 'contents' },\n },\n children\n ),\n h('span', {\n ref: targetRef,\n style: { display: translated.value ? 'contents' : 'none' },\n }),\n ]);\n };\n },\n});\n\nexport const T = Translate;\n\n// The plugin registers <T> globally, so templates type-check without importing it.\ndeclare module 'vue' {\n export interface GlobalComponents {\n T: typeof Translate;\n }\n}\n","import {\n App,\n computed,\n ComputedRef,\n inject,\n InjectionKey,\n readonly,\n ref,\n Ref,\n} from 'vue';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n Language,\n libraryWebsite,\n MagicTranslateConfig,\n} from '@magic-translate/core';\nimport { Translate } from './translate';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport type TranslateParams = Omit<DataLoaderLoadParams, 'text'>;\n\nexport interface MagicTranslate {\n /**\n * The language content is currently translated to.\n */\n readonly language: Readonly<Ref<Languages>>;\n readonly apiKey: string;\n readonly sourceLanguage?: Languages;\n /** @ignore */\n readonly dataLoader: ComputedRef<\n DataLoader<DataLoaderLoadParams, string, string>\n >;\n setLanguage(language: Languages): void;\n translate(text: string, params?: TranslateParams): Promise<string>;\n /** @ignore */\n cacheKey(text: string, params?: TranslateParams): string;\n install(app: App): void;\n}\n\nexport const MAGIC_TRANSLATE: InjectionKey<MagicTranslate> =\n Symbol('magic-translate');\n\n/**\n * Sets up Magic Translate. Install the result as a plugin with `app.use()`.\n * It can also be used directly, outside of components.\n */\nexport function createMagicTranslate(\n config: MagicTranslateConfig\n): MagicTranslate {\n if (!config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n\n const { debug, apiKey, sourceLanguage, _apiUrl, formality } = config;\n const loaderFactory = config._loaderFactory ?? defaultLoaderFactory;\n const language = ref<Languages>(config.language);\n\n const dataLoader = computed(\n () =>\n new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n lang: language.value,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language.value) }\n )\n );\n\n const magicTranslate: MagicTranslate = {\n language: readonly(language),\n apiKey,\n sourceLanguage,\n dataLoader,\n setLanguage(lang) {\n language.value = lang;\n },\n translate(text, params = {}) {\n return dataLoader.value.load({ text, ...params });\n },\n cacheKey(text, params = {}) {\n return cacheKeyFn(\n apiKey,\n sourceLanguage,\n language.value\n )({\n text,\n ...params,\n });\n },\n install(app) {\n app.provide(MAGIC_TRANSLATE, magicTranslate);\n app.component('T', Translate);\n },\n };\n\n return magicTranslate;\n}\n\nexport function useMagicTranslate(): MagicTranslate {\n const magicTranslate = inject(MAGIC_TRANSLATE, null);\n\n if (magicTranslate === null) {\n throw new Error(\n 'useMagicTranslate must be used in an app that installed createMagicTranslate()'\n );\n }\n\n return magicTranslate;\n}\n\nexport function useLanguageSwitcher(): {\n setLanguage: (language: Languages) => void;\n currentLanguage: Readonly<Ref<Languages>>;\n} {\n const { setLanguage, language } = useMagicTranslate();\n\n return { setLanguage, currentLanguage: language };\n}\n","import { reactive } from 'vue';\nimport { TranslateParams, useMagicTranslate } from './magic-translate';\n\n/**\n * Returns a function that translates plain strings, e.g. for attributes:\n * `<input :placeholder=\"t('Search')\" />`. It returns the original text until\n * the translation has loaded, and the component re-renders once it has.\n */\nexport function useTranslate(): (\n text: string,\n params?: TranslateParams\n) => string {\n const magicTranslate = useMagicTranslate();\n const translations = reactive(new Map<string, string>());\n const pending = new Set<string>();\n\n return (text, params = {}) => {\n // Translations are only loaded in the browser.\n if (!text || typeof window === 'undefined') {\n return text;\n }\n\n // reads the current language, so a language switch re-renders\n const key = magicTranslate.cacheKey(text, params);\n const translation = translations.get(key);\n\n if (translation === undefined && !pending.has(key)) {\n pending.add(key);\n magicTranslate.translate(text, params).then((translated) => {\n translations.set(key, translated);\n pending.delete(key);\n });\n }\n\n return translation ?? text;\n };\n}\n\nexport const useT = useTranslate;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["readSource","clone","element","comments","scopeAttribute","walker","node","el","name","comment","leadingWhitespace","html","trailingWhitespace","assertNoComponents","children","child","isVNode","vnode","Text","Comment","Translate","defineComponent","magicTranslate","useMagicTranslate","sourceRef","ref","targetRef","source","shallowRef","translated","lastRequestId","lastTemplate","observer","render","target","translation","values","fillElement","onMounted","read","next","current","onBeforeUnmount","watch","props","lang","formality","variables","toHtmlTemplate","template","warnAboutUnused","unused","requestId","slots","h","T","MAGIC_TRANSLATE","createMagicTranslate","config","libraryWebsite","loaderFactory","defaultLoaderFactory","language","dataLoader","computed","DataLoader","debug","sourceLanguage","_apiUrl","cacheKeyFn","apiKey","readonly","params","v","toTemplate","text","fillPlaceholders","app","inject","useLanguageSwitcher","useTranslate","translations","reactive","pending","key","useT"],"sources":["../src/translate.ts","../src/magic-translate.ts","../src/use-translate.ts"],"sourcesContent":["import {\n Comment,\n defineComponent,\n h,\n isVNode,\n onBeforeUnmount,\n onMounted,\n PropType,\n ref,\n shallowRef,\n Text,\n VNode,\n watch,\n} from 'vue';\nimport {\n fillElement,\n toHtmlTemplate,\n TranslateOptions,\n Variables,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { Languages, useMagicTranslate } from './magic-translate';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `data-v-*` attribute of the parent's scoped styles, so they still\n // apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the slot content into the HTML string that gets translated, without\n * the comments and attributes Vue adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('data-v-')) {\n scopeAttribute = name;\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\nfunction assertNoComponents(children: unknown) {\n if (!Array.isArray(children)) {\n return;\n }\n\n for (const child of children) {\n if (!isVNode(child)) {\n assertNoComponents(child);\n continue;\n }\n\n const vnode = child as VNode;\n if (vnode.type === Text || vnode.type === Comment) {\n continue;\n }\n if (typeof vnode.type === 'object' || typeof vnode.type === 'function') {\n const name =\n (vnode.type as { name?: string; __name?: string }).name ??\n (vnode.type as { __name?: string }).__name;\n throw new Error(\n `Components are not allowed inside \\`<T>...</T>\\` components.${\n name ? ` The component found inside the T component is ${name}.` : ''\n }`\n );\n }\n // elements and fragments\n assertNoComponents(vnode.children);\n }\n}\n\n/**\n * Translates its content, which can be text and plain HTML elements. The\n * original content is shown until the translation has loaded.\n */\nexport const Translate = defineComponent({\n name: 'T',\n props: {\n lang: String as PropType<Languages>,\n formality: String as PropType<TranslateOptions['formality']>,\n /**\n * Values that are not translated, referenced as `$name` in the content.\n * The text is translated once as a template and cached, whatever the\n * values are.\n */\n v: Object as PropType<Variables>,\n },\n setup(props, { slots }) {\n const magicTranslate = useMagicTranslate();\n const sourceRef = ref<HTMLElement>();\n const targetRef = ref<HTMLElement>();\n const source = shallowRef<Source | null>(null);\n const translated = ref(false);\n let lastRequestId = 0;\n let lastTemplate: string | undefined;\n let observer: MutationObserver | undefined;\n\n const render = (\n source: Source,\n translation: string,\n values: readonly string[] = []\n ) => {\n const target = targetRef.value;\n if (!target) {\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${translation}${source.trailingWhitespace}`;\n if (values.length > 0) {\n fillElement(target, values);\n }\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n translated.value = true;\n };\n\n onMounted(() => {\n const element = sourceRef.value!;\n const read = () => {\n const next = readSource(element);\n const current = source.value;\n if (\n next.html !== current?.html ||\n next.leadingWhitespace !== current.leadingWhitespace ||\n next.trailingWhitespace !== current.trailingWhitespace\n ) {\n source.value = next;\n }\n };\n read();\n\n // Picks up changes to the slot content, such as interpolations.\n observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n });\n\n onBeforeUnmount(() => observer?.disconnect());\n\n watch(\n [\n source,\n () => props.lang,\n () => props.formality,\n () => props.v,\n // a language switch has to re-translate the content\n magicTranslate.language,\n ],\n ([source, lang, formality, variables]) => {\n if (!source?.html) {\n translated.value = false;\n return;\n }\n\n const { template, values, unused } = variables\n ? toHtmlTemplate(source.html, variables)\n : { template: source.html, values: [], unused: [] };\n if (template !== lastTemplate) {\n lastTemplate = template;\n warnAboutUnused(unused);\n }\n if (variables && !translated.value) {\n render(source, template, values);\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++lastRequestId;\n magicTranslate\n .translate(template, { lang, formality })\n .then((translation) => {\n if (requestId === lastRequestId) {\n render(source, translation, values);\n }\n });\n }\n );\n\n return () => {\n const children = slots.default?.();\n assertNoComponents(children);\n\n return h('span', { style: 'display: contents' }, [\n h(\n 'span',\n {\n ref: sourceRef,\n // With variables, the source would show `$name`, so it stays\n // hidden and the text is shown with the values filled in until\n // the translation arrives.\n style: {\n display: translated.value || props.v ? 'none' : 'contents',\n },\n },\n children\n ),\n h('span', {\n ref: targetRef,\n style: { display: translated.value ? 'contents' : 'none' },\n }),\n ]);\n };\n },\n});\n\nexport const T = Translate;\n\n// The plugin registers <T> globally, so templates type-check without importing it.\ndeclare module 'vue' {\n export interface GlobalComponents {\n T: typeof Translate;\n }\n}\n","import {\n App,\n computed,\n ComputedRef,\n inject,\n InjectionKey,\n readonly,\n ref,\n Ref,\n} from 'vue';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n fillPlaceholders,\n Language,\n libraryWebsite,\n MagicTranslateConfig,\n toTemplate,\n Variables,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { Translate } from './translate';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport interface TranslateParams extends Omit<DataLoaderLoadParams, 'text'> {\n /**\n * Values that are not translated, referenced as `$name` in the text. The text\n * is translated once as a template and cached, whatever the values are.\n */\n v?: Variables;\n}\n\nexport interface MagicTranslate {\n /**\n * The language content is currently translated to.\n */\n readonly language: Readonly<Ref<Languages>>;\n readonly apiKey: string;\n readonly sourceLanguage?: Languages;\n /** @ignore */\n readonly dataLoader: ComputedRef<\n DataLoader<DataLoaderLoadParams, string, string>\n >;\n setLanguage(language: Languages): void;\n translate(text: string, params?: TranslateParams): Promise<string>;\n /** @ignore */\n cacheKey(text: string, params?: Omit<TranslateParams, 'v'>): string;\n install(app: App): void;\n}\n\nexport const MAGIC_TRANSLATE: InjectionKey<MagicTranslate> =\n Symbol('magic-translate');\n\n/**\n * Sets up Magic Translate. Install the result as a plugin with `app.use()`.\n * It can also be used directly, outside of components.\n */\nexport function createMagicTranslate(\n config: MagicTranslateConfig\n): MagicTranslate {\n if (!config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n\n const { debug, apiKey, sourceLanguage, _apiUrl, formality } = config;\n const loaderFactory = config._loaderFactory ?? defaultLoaderFactory;\n const language = ref<Languages>(config.language);\n\n const dataLoader = computed(\n () =>\n new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n lang: language.value,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language.value) }\n )\n );\n\n const magicTranslate: MagicTranslate = {\n language: readonly(language),\n apiKey,\n sourceLanguage,\n dataLoader,\n setLanguage(lang) {\n language.value = lang;\n },\n translate(text, { v, ...params } = {}) {\n if (!v) {\n return dataLoader.value.load({ text, ...params });\n }\n const { template, values, unused } = toTemplate(text, v);\n warnAboutUnused(unused);\n return dataLoader.value\n .load({ text: template, ...params })\n .then((translation) => fillPlaceholders(translation, values));\n },\n cacheKey(text, params = {}) {\n return cacheKeyFn(\n apiKey,\n sourceLanguage,\n language.value\n )({\n text,\n ...params,\n });\n },\n install(app) {\n app.provide(MAGIC_TRANSLATE, magicTranslate);\n app.component('T', Translate);\n },\n };\n\n return magicTranslate;\n}\n\nexport function useMagicTranslate(): MagicTranslate {\n const magicTranslate = inject(MAGIC_TRANSLATE, null);\n\n if (magicTranslate === null) {\n throw new Error(\n 'useMagicTranslate must be used in an app that installed createMagicTranslate()'\n );\n }\n\n return magicTranslate;\n}\n\nexport function useLanguageSwitcher(): {\n setLanguage: (language: Languages) => void;\n currentLanguage: Readonly<Ref<Languages>>;\n} {\n const { setLanguage, language } = useMagicTranslate();\n\n return { setLanguage, currentLanguage: language };\n}\n","import { reactive } from 'vue';\nimport {\n fillPlaceholders,\n toTemplate,\n warnAboutUnused,\n} from '@magic-translate/core';\nimport { TranslateParams, useMagicTranslate } from './magic-translate';\n\n/**\n * Returns a function that translates plain strings, e.g. for attributes:\n * `<input :placeholder=\"t('Search')\" />`. It returns the original text until\n * the translation has loaded, and the component re-renders once it has.\n *\n * Values that should not be translated go in `v` and are referenced as `$name`:\n * `t('Search $count products', { v: { count } })`.\n */\nexport function useTranslate(): (\n text: string,\n params?: TranslateParams\n) => string {\n const magicTranslate = useMagicTranslate();\n const translations = reactive(new Map<string, string>());\n const pending = new Set<string>();\n\n return (text, { v, ...params } = {}) => {\n // Only the template is translated, so new values reuse the translation.\n const { template, values, unused } = v\n ? toTemplate(text, v)\n : { template: text, values: [], unused: [] };\n\n // Translations are only loaded in the browser.\n if (!text || typeof window === 'undefined') {\n return fillPlaceholders(template, values);\n }\n\n // reads the current language, so a language switch re-renders\n const key = magicTranslate.cacheKey(template, params);\n const translation = translations.get(key);\n\n if (translation === undefined && !pending.has(key)) {\n warnAboutUnused(unused);\n pending.add(key);\n magicTranslate.translate(template, params).then((translated) => {\n translations.set(key, translated);\n pending.delete(key);\n });\n }\n\n return fillPlaceholders(translation ?? template, values);\n };\n}\n\nexport const useT = useTranslate;\n"],"mappings":"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"}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { App, ComputedRef, InjectionKey, Ref } from 'vue';
|
|
2
|
-
import { DataLoader, DataLoaderLoadParams, Language, MagicTranslateConfig } from '@magic-translate/core';
|
|
2
|
+
import { DataLoader, DataLoaderLoadParams, Language, MagicTranslateConfig, Variables } from '@magic-translate/core';
|
|
3
3
|
/**
|
|
4
4
|
* Type alias to make the language enum more flexible to also allow passing language strings
|
|
5
5
|
* instead of just the enum.
|
|
6
6
|
*/
|
|
7
7
|
export type Languages = `${Language}`;
|
|
8
|
-
export
|
|
8
|
+
export interface TranslateParams extends Omit<DataLoaderLoadParams, 'text'> {
|
|
9
|
+
/**
|
|
10
|
+
* Values that are not translated, referenced as `$name` in the text. The text
|
|
11
|
+
* is translated once as a template and cached, whatever the values are.
|
|
12
|
+
*/
|
|
13
|
+
v?: Variables;
|
|
14
|
+
}
|
|
9
15
|
export interface MagicTranslate {
|
|
10
16
|
/**
|
|
11
17
|
* The language content is currently translated to.
|
|
@@ -18,7 +24,7 @@ export interface MagicTranslate {
|
|
|
18
24
|
setLanguage(language: Languages): void;
|
|
19
25
|
translate(text: string, params?: TranslateParams): Promise<string>;
|
|
20
26
|
/** @ignore */
|
|
21
|
-
cacheKey(text: string, params?: TranslateParams): string;
|
|
27
|
+
cacheKey(text: string, params?: Omit<TranslateParams, 'v'>): string;
|
|
22
28
|
install(app: App): void;
|
|
23
29
|
}
|
|
24
30
|
export declare const MAGIC_TRANSLATE: InjectionKey<MagicTranslate>;
|
package/dist/translate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PropType, VNode } from 'vue';
|
|
2
|
-
import { TranslateOptions } from '@magic-translate/core';
|
|
2
|
+
import { TranslateOptions, Variables } from '@magic-translate/core';
|
|
3
3
|
import { Languages } from './magic-translate';
|
|
4
4
|
/**
|
|
5
5
|
* Translates its content, which can be text and plain HTML elements. The
|
|
@@ -8,20 +8,44 @@ import { Languages } from './magic-translate';
|
|
|
8
8
|
export declare const Translate: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
9
9
|
lang: PropType<Languages>;
|
|
10
10
|
formality: PropType<TranslateOptions["formality"]>;
|
|
11
|
+
/**
|
|
12
|
+
* Values that are not translated, referenced as `$name` in the content.
|
|
13
|
+
* The text is translated once as a template and cached, whatever the
|
|
14
|
+
* values are.
|
|
15
|
+
*/
|
|
16
|
+
v: PropType<Variables>;
|
|
11
17
|
}>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
12
18
|
[key: string]: any;
|
|
13
19
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
14
20
|
lang: PropType<Languages>;
|
|
15
21
|
formality: PropType<TranslateOptions["formality"]>;
|
|
22
|
+
/**
|
|
23
|
+
* Values that are not translated, referenced as `$name` in the content.
|
|
24
|
+
* The text is translated once as a template and cached, whatever the
|
|
25
|
+
* values are.
|
|
26
|
+
*/
|
|
27
|
+
v: PropType<Variables>;
|
|
16
28
|
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
17
29
|
export declare const T: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
18
30
|
lang: PropType<Languages>;
|
|
19
31
|
formality: PropType<TranslateOptions["formality"]>;
|
|
32
|
+
/**
|
|
33
|
+
* Values that are not translated, referenced as `$name` in the content.
|
|
34
|
+
* The text is translated once as a template and cached, whatever the
|
|
35
|
+
* values are.
|
|
36
|
+
*/
|
|
37
|
+
v: PropType<Variables>;
|
|
20
38
|
}>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
21
39
|
[key: string]: any;
|
|
22
40
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
23
41
|
lang: PropType<Languages>;
|
|
24
42
|
formality: PropType<TranslateOptions["formality"]>;
|
|
43
|
+
/**
|
|
44
|
+
* Values that are not translated, referenced as `$name` in the content.
|
|
45
|
+
* The text is translated once as a template and cached, whatever the
|
|
46
|
+
* values are.
|
|
47
|
+
*/
|
|
48
|
+
v: PropType<Variables>;
|
|
25
49
|
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
26
50
|
declare module 'vue' {
|
|
27
51
|
interface GlobalComponents {
|
package/dist/use-translate.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { TranslateParams } from './magic-translate';
|
|
|
3
3
|
* Returns a function that translates plain strings, e.g. for attributes:
|
|
4
4
|
* `<input :placeholder="t('Search')" />`. It returns the original text until
|
|
5
5
|
* the translation has loaded, and the component re-renders once it has.
|
|
6
|
+
*
|
|
7
|
+
* Values that should not be translated go in `v` and are referenced as `$name`:
|
|
8
|
+
* `t('Search $count products', { v: { count } })`.
|
|
6
9
|
*/
|
|
7
10
|
export declare function useTranslate(): (text: string, params?: TranslateParams) => string;
|
|
8
11
|
export declare const useT: typeof useTranslate;
|
package/package.json
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic-translate/vue",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"vue",
|
|
6
|
+
"vue3",
|
|
7
|
+
"vue-i18n",
|
|
6
8
|
"nuxt",
|
|
7
|
-
"
|
|
9
|
+
"vue-plugin",
|
|
8
10
|
"i18n",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
+
"internationalization",
|
|
12
|
+
"localization",
|
|
13
|
+
"l10n",
|
|
14
|
+
"translation",
|
|
15
|
+
"translate",
|
|
16
|
+
"ai-translation",
|
|
17
|
+
"machine-translation",
|
|
18
|
+
"multilingual",
|
|
19
|
+
"magic-translate"
|
|
11
20
|
],
|
|
12
|
-
"description": "
|
|
21
|
+
"description": "AI-powered i18n for Vue 3 and Nuxt. Wrap text in <T> and it is translated automatically, with no translation keys or language files.",
|
|
13
22
|
"main": "dist/index.cjs",
|
|
14
23
|
"module": "dist/index.js",
|
|
15
24
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +39,7 @@
|
|
|
30
39
|
"url": "git+https://github.com/magic-translate/magic-translate.git",
|
|
31
40
|
"directory": "packages/vue"
|
|
32
41
|
},
|
|
33
|
-
"homepage": "https://magictranslate.io",
|
|
42
|
+
"homepage": "https://magictranslate.io/docs/vue",
|
|
34
43
|
"license": "MIT",
|
|
35
44
|
"engines": {
|
|
36
45
|
"node": ">=22.22.1"
|
|
@@ -39,20 +48,14 @@
|
|
|
39
48
|
"vue": ">=3.3.0"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
42
|
-
"@magic-translate/core": "^1.
|
|
51
|
+
"@magic-translate/core": "^1.5.0"
|
|
43
52
|
},
|
|
44
53
|
"devDependencies": {
|
|
45
|
-
"@eslint/js": "^10.0.1",
|
|
46
54
|
"@types/node": "^26.6.5",
|
|
47
55
|
"@vitest/coverage-v8": "^5.0.5",
|
|
48
56
|
"@vue/test-utils": "^2.5.1",
|
|
49
|
-
"eslint": "^10.13.0",
|
|
50
|
-
"eslint-config-prettier": "^10.1.8",
|
|
51
|
-
"globals": "^17.13.0",
|
|
52
57
|
"jsdom": "^30.1.3",
|
|
53
|
-
"prettier": "^3.9.10",
|
|
54
58
|
"typescript": "~6.0.3",
|
|
55
|
-
"typescript-eslint": "^8.72.0",
|
|
56
59
|
"vite": "^8.3.5",
|
|
57
60
|
"vitest": "^5.0.5",
|
|
58
61
|
"vue": "^3.5.44"
|
|
@@ -64,7 +67,6 @@
|
|
|
64
67
|
"build": "vite build && tsc -p tsconfig.build.json",
|
|
65
68
|
"start": "vite build --watch",
|
|
66
69
|
"test": "vitest run --coverage",
|
|
67
|
-
"test:watch": "vitest"
|
|
68
|
-
"lint": "eslint ."
|
|
70
|
+
"test:watch": "vitest"
|
|
69
71
|
}
|
|
70
72
|
}
|