Fix unstyled editor language selector, order all language menus logically
LangSelect's scoped CSS landed on the shared store chunk, whose stylesheet the editor never loaded (only the public selector path injected it), so the editor's language selector rendered unstyled on untranslated pages. Collect editor stylesheets from the entry's imported chunks too (same traversal as the langselect assets). Also: hreflang alternates now skip languages disabled site-wide, and all selectors (page editor, structure tab, public selector) order languages the same way — primary first, then the lang tab's geographic grouping.
This commit is contained in:
@@ -9,23 +9,28 @@
|
||||
// store and re-renders it).
|
||||
import { computed } from 'vue'
|
||||
import LangSelect from './LangSelect.vue'
|
||||
import { flagFor, langName } from './langs'
|
||||
import { flagFor, langName, langSort } from './langs'
|
||||
import { useStore } from './store'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
// The "(primary)" marker is admin-panel information; the public selector
|
||||
// lists plain languages.
|
||||
const options = computed(() =>
|
||||
store.langAlternates.map((a) => ({
|
||||
tag: a.tag,
|
||||
code: a.tag,
|
||||
name: langName(a.tag),
|
||||
flag: flagFor(a.tag),
|
||||
primary: false,
|
||||
})),
|
||||
)
|
||||
// lists plain languages. Order: the primary language first, then the rest
|
||||
// in the lang tab's geographic grouping (./langs langSort) — the head's
|
||||
// hreflang order is just alphabetical.
|
||||
const primaryTag = computed(() => store.langAlternates.find((a) => a.primary)?.tag ?? '')
|
||||
const options = computed(() => {
|
||||
const rest = langSort(
|
||||
store.langAlternates.map((a) => a.tag).filter((t) => t !== primaryTag.value),
|
||||
)
|
||||
return [primaryTag.value, ...rest].filter(Boolean).map((tag) => ({
|
||||
tag,
|
||||
code: tag,
|
||||
name: langName(tag),
|
||||
flag: flagFor(tag),
|
||||
primary: false,
|
||||
}))
|
||||
})
|
||||
// The explicit pick, else the served language (header-autodetected pages
|
||||
// may have neither), else the primary.
|
||||
const model = computed(() => store.lang || store.servedLang || primaryTag.value)
|
||||
|
||||
@@ -33,7 +33,7 @@ import { keymap } from '@codemirror/view'
|
||||
import { indentWithTab } from '@codemirror/commands'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
import { cmHighlight, cmTheme } from './cmtheme'
|
||||
import { flagFor, langName } from './langs'
|
||||
import { flagFor, langName, langSort } from './langs'
|
||||
import { editorLang, pagePrimary } from './editorLang'
|
||||
import LangSelect from './LangSelect.vue'
|
||||
import ConnNote from './ConnNote.vue'
|
||||
@@ -121,11 +121,13 @@ function normPath(p) {
|
||||
// localization settings tab).
|
||||
|
||||
// The picker's options: the primary language first, then the union of the
|
||||
// page's translations and the site-wide configured targets, sorted.
|
||||
// page's translations and the site-wide configured targets in the lang
|
||||
// tab's geographic grouping (./langs langSort).
|
||||
const langOptions = computed(() => {
|
||||
const others = [...new Set([...siteLangs.value, ...pageLangs.value])]
|
||||
.filter((l) => l && l !== primaryLang.value)
|
||||
.sort()
|
||||
const others = langSort(
|
||||
[...new Set([...siteLangs.value, ...pageLangs.value])]
|
||||
.filter((l) => l && l !== primaryLang.value),
|
||||
)
|
||||
return [primaryLang.value, ...others].map((code) => ({
|
||||
tag: code === primaryLang.value ? '' : code,
|
||||
code,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { computed, inject, onActivated, onMounted, onUnmounted, provide, ref, wa
|
||||
import StructureTree from './StructureTree.vue'
|
||||
import LangSelect from './LangSelect.vue'
|
||||
import { slugify } from './slugify'
|
||||
import { flagFor, langName } from './langs'
|
||||
import { flagFor, langName, langSort } from './langs'
|
||||
import { editorLang, pagePrimary } from './editorLang'
|
||||
import { dropPageCache, loadPlain } from './swapdoc'
|
||||
|
||||
@@ -40,9 +40,10 @@ const primaryLang = ref('en')
|
||||
const siteLangs = ref([])
|
||||
|
||||
// The strip's options: the primary language first, then the configured
|
||||
// translation targets (the lang tab manages that set).
|
||||
// translation targets (the lang tab manages that set) in the lang tab's
|
||||
// geographic grouping (./langs langSort).
|
||||
const langOptions = computed(() =>
|
||||
[primaryLang.value, ...siteLangs.value.filter((l) => l !== primaryLang.value)]
|
||||
[primaryLang.value, ...langSort(siteLangs.value.filter((l) => l !== primaryLang.value))]
|
||||
.map((code) => ({
|
||||
tag: code === primaryLang.value ? '' : code,
|
||||
code,
|
||||
@@ -63,7 +64,7 @@ watch(lang, () => refreshPages())
|
||||
// dropdown lists "inherit" first (naming what it resolves to), then every
|
||||
// site language. Setting it on a section covers its whole subtree.
|
||||
const rowLangChoices = computed(() =>
|
||||
[primaryLang.value, ...siteLangs.value.filter((l) => l !== primaryLang.value)]
|
||||
[primaryLang.value, ...langSort(siteLangs.value.filter((l) => l !== primaryLang.value))]
|
||||
.map((code) => ({ tag: code, code, name: langName(code), flag: flagFor(code), primary: false })),
|
||||
)
|
||||
function rowLangOptions(el) {
|
||||
|
||||
@@ -30,6 +30,20 @@ export const LANG_GROUPS = [
|
||||
|
||||
const displayNames = new Intl.DisplayNames(['en'], { type: 'language' })
|
||||
|
||||
// Consistent menu ordering for language selectors: the geographic/cultural
|
||||
// grouping above (similar languages sit together, and it does not vary with
|
||||
// the display language the way alphabetical-by-name would). Tags outside
|
||||
// the groups trail, ordered by tag. The primary language is not special
|
||||
// here — callers put it first themselves.
|
||||
const groupOrder = new Map(LANG_GROUPS.flat().map((c, i) => [c, i]))
|
||||
export function langSort(codes) {
|
||||
return [...codes].sort(
|
||||
(a, b) =>
|
||||
(groupOrder.get(a) ?? groupOrder.size) - (groupOrder.get(b) ?? groupOrder.size)
|
||||
|| a.localeCompare(b),
|
||||
)
|
||||
}
|
||||
|
||||
// English display name for a language tag ("fi" -> "Finnish").
|
||||
export function langName(tag) {
|
||||
try {
|
||||
|
||||
+22
-8
@@ -263,20 +263,31 @@ def _transition_css_url(transition: str) -> str | None:
|
||||
|
||||
|
||||
def _editor_css_url(vite_url: str | None) -> str | None:
|
||||
"""URL for the editor-specific stylesheet (Vue component styles).
|
||||
"""URLs (comma-joined) for the editor-specific stylesheets (Vue
|
||||
component styles).
|
||||
|
||||
This is linked by the public-page edit pen so the editor styles are
|
||||
loaded before the editor JS dynamic-import resolves.
|
||||
loaded before the editor JS dynamic-import resolves. Component styles
|
||||
can land on shared chunks rather than the entry's own stylesheet —
|
||||
LangSelect's ride on the shared store chunk, as it is also used by the
|
||||
on-demand public language selector — so collect the stylesheets of the
|
||||
entry and its imported chunks (the same traversal _langselect_assets
|
||||
does).
|
||||
"""
|
||||
if vite_url:
|
||||
return None
|
||||
manifest = _manifest()
|
||||
entry = manifest["src/main.js"]
|
||||
base = manifest.get(_BASE_CSS_KEY, {}).get("file")
|
||||
for css in entry.get("css", []):
|
||||
if css != base:
|
||||
return f"/{css}"
|
||||
return None
|
||||
stylesheets, seen = [], set()
|
||||
queue = ["src/main.js"]
|
||||
for key in queue: # grows with imported chunks
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
entry = manifest[key]
|
||||
stylesheets += [f"/{css}" for css in entry.get("css", []) if css != base]
|
||||
queue += entry.get("imports", [])
|
||||
return ",".join(stylesheets) or None
|
||||
|
||||
|
||||
def _inline_asset(url: str) -> str:
|
||||
@@ -1047,9 +1058,12 @@ def _language_urls(
|
||||
canonical = url if lang == original else f"{url}?lang={lang}"
|
||||
alternates = []
|
||||
if data.translate_langs:
|
||||
# Only languages the page actually has AND that are still enabled
|
||||
# site-wide (a disabled target stops being advertised).
|
||||
enabled = {original, *data.translate_langs}
|
||||
alternates = [("x-default", url)] + [
|
||||
(tag, url if tag == original else f"{url}?lang={tag}")
|
||||
for tag in sorted({original, *node.langs})
|
||||
for tag in sorted({original, *node.langs} & enabled)
|
||||
]
|
||||
return canonical, alternates
|
||||
|
||||
|
||||
Reference in New Issue
Block a user