ui: add B2B olmayan stok (orphans) page
This commit is contained in:
157
ui/src/composables/useRuntimeDomTranslations.js
Normal file
157
ui/src/composables/useRuntimeDomTranslations.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { nextTick, onBeforeUnmount, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { DEFAULT_LOCALE } from 'src/i18n/languages'
|
||||
import { useLocaleStore } from 'src/stores/localeStore'
|
||||
import { useRuntimeTranslationStore } from 'src/stores/runtimeTranslationStore'
|
||||
|
||||
const textOriginals = new WeakMap()
|
||||
const attrOriginals = new WeakMap()
|
||||
const TRANSLATABLE_ATTRS = ['placeholder', 'title', 'aria-label']
|
||||
const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA'])
|
||||
|
||||
function normalizeRuntimeText (value) {
|
||||
return String(value || '').trim().replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
function preserveOuterWhitespace (original, translated) {
|
||||
const text = String(original || '')
|
||||
const leading = text.match(/^\s*/)?.[0] || ''
|
||||
const trailing = text.match(/\s*$/)?.[0] || ''
|
||||
return `${leading}${translated}${trailing}`
|
||||
}
|
||||
|
||||
function shouldSkipNode (node) {
|
||||
const parent = node?.parentElement
|
||||
if (!parent) return true
|
||||
if (SKIP_TAGS.has(parent.tagName)) return true
|
||||
if (parent.closest?.('[data-no-runtime-i18n="true"]')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function useRuntimeDomTranslations () {
|
||||
const route = useRoute()
|
||||
const localeStore = useLocaleStore()
|
||||
const runtimeTranslations = useRuntimeTranslationStore()
|
||||
let observer = null
|
||||
let applyTimer = null
|
||||
|
||||
function translateTextNode (node) {
|
||||
if (shouldSkipNode(node)) return
|
||||
const current = String(node.nodeValue || '')
|
||||
if (!normalizeRuntimeText(current)) return
|
||||
|
||||
if (!textOriginals.has(node)) {
|
||||
textOriginals.set(node, current)
|
||||
}
|
||||
const original = textOriginals.get(node)
|
||||
const key = normalizeRuntimeText(original)
|
||||
const translated = localeStore.locale === DEFAULT_LOCALE
|
||||
? key
|
||||
: runtimeTranslations.byText[key]
|
||||
|
||||
const nextValue = preserveOuterWhitespace(original, translated || key)
|
||||
if (node.nodeValue !== nextValue) {
|
||||
node.nodeValue = nextValue
|
||||
}
|
||||
}
|
||||
|
||||
function translateElementAttrs (el) {
|
||||
if (!el || el.nodeType !== Node.ELEMENT_NODE) return
|
||||
if (el.closest?.('[data-no-runtime-i18n="true"]')) return
|
||||
let originals = attrOriginals.get(el)
|
||||
if (!originals) {
|
||||
originals = {}
|
||||
attrOriginals.set(el, originals)
|
||||
}
|
||||
|
||||
for (const attr of TRANSLATABLE_ATTRS) {
|
||||
if (!el.hasAttribute(attr)) continue
|
||||
const current = el.getAttribute(attr)
|
||||
if (!normalizeRuntimeText(current)) continue
|
||||
if (!Object.prototype.hasOwnProperty.call(originals, attr)) {
|
||||
originals[attr] = current
|
||||
}
|
||||
const key = normalizeRuntimeText(originals[attr])
|
||||
const translated = localeStore.locale === DEFAULT_LOCALE
|
||||
? key
|
||||
: runtimeTranslations.byText[key]
|
||||
const nextValue = translated || key
|
||||
if (el.getAttribute(attr) !== nextValue) {
|
||||
el.setAttribute(attr, nextValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function translateRoot (root = document.body) {
|
||||
if (typeof document === 'undefined' || !root) return
|
||||
|
||||
const walker = document.createTreeWalker(
|
||||
root,
|
||||
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
||||
{
|
||||
acceptNode (node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return normalizeRuntimeText(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let node = walker.currentNode
|
||||
while (node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
translateTextNode(node)
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
translateElementAttrs(node)
|
||||
}
|
||||
node = walker.nextNode()
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleApply () {
|
||||
if (applyTimer) clearTimeout(applyTimer)
|
||||
applyTimer = setTimeout(async () => {
|
||||
applyTimer = null
|
||||
await nextTick()
|
||||
translateRoot(document.body)
|
||||
}, 30)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await runtimeTranslations.loadLocale(localeStore.locale)
|
||||
scheduleApply()
|
||||
observer = new MutationObserver(() => scheduleApply())
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: TRANSLATABLE_ATTRS
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
() => localeStore.locale,
|
||||
async (locale) => {
|
||||
await runtimeTranslations.loadLocale(locale)
|
||||
scheduleApply()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => scheduleApply()
|
||||
)
|
||||
|
||||
watch(
|
||||
() => runtimeTranslations.version,
|
||||
() => scheduleApply()
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (observer) observer.disconnect()
|
||||
if (applyTimer) clearTimeout(applyTimer)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user