ui: add B2B olmayan stok (orphans) page

This commit is contained in:
M_Kececi
2026-06-25 14:14:09 +03:00
parent 52b39725ec
commit dfad548963
19 changed files with 594 additions and 71 deletions

View 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)
})
}