Translate more editor UI surfaces

This commit is contained in:
Danila Poyarkov 2026-03-25 18:47:23 +03:00
parent 9f296b7357
commit 4b9cd54983
13 changed files with 118 additions and 56 deletions

View file

@ -90,6 +90,8 @@ export const panelMessages = i18n('panels', {
page: 'Page',
position: 'Position',
layout: 'Layout',
autoLayout: 'Auto layout',
alignment: 'Alignment',
appearance: 'Appearance',
fill: 'Fill',
stroke: 'Stroke',
@ -113,6 +115,26 @@ export const panelMessages = i18n('panels', {
noLocalVariables: 'No local variables',
openVariables: 'Open variables',
addPage: 'Add page',
toggleVisibility: 'Toggle visibility',
independentCornerRadii: 'Independent corner radii',
detachVariable: 'Detach variable',
applyVariable: 'Apply variable',
noVariablesFound: 'No variables found',
addAutoLayout: 'Add auto layout (Shift+A)',
removeAutoLayout: 'Remove auto layout',
alignLeft: 'Align left',
alignCenterHorizontally: 'Align center horizontally',
alignRight: 'Align right',
alignTop: 'Align top',
alignCenterVertically: 'Align center vertically',
alignBottom: 'Align bottom',
flipHorizontal: 'Flip horizontal',
flipVertical: 'Flip vertical',
rotate90: 'Rotate 90°',
mixedFillsHelp: 'Click + to replace mixed fills',
mixedStrokesHelp: 'Click + to replace mixed strokes',
mixedEffectsHelp: 'Click + to replace mixed effects',
strokeSides: 'Stroke sides',
mixed: 'Mixed',
layersCount: params('{count} layers'),
goToMainComponent: 'Go to Main Component',
@ -122,13 +144,11 @@ export const panelMessages = i18n('panels', {
linearGradient: 'Linear',
radialGradient: 'Radial',
image: 'Image',
stops: 'Stops',
addStop: 'Add stop',
alignLeft: 'Align left',
alignCenter: 'Align center',
alignRight: 'Align right',
alignTop: 'Align top',
alignMiddle: 'Align middle',
alignBottom: 'Align bottom'
alignMiddle: 'Align middle'
})
export const pageMessages = i18n('pages', {
@ -157,6 +177,8 @@ export const dialogMessages = i18n('dialogs', {
describeCreateOrChange: 'Describe what you want to create or change.',
stopGenerating: 'Stop generating',
sendMessage: 'Send message',
baseURLPlaceholder: 'Base URL (e.g. http://localhost:11434/v1)',
modelIDPlaceholder: 'Model ID (e.g. llama-3.3-70b)',
search: 'Search…',
noResults: 'No results',
share: 'Share'

View file

@ -78,6 +78,8 @@
"page": "Страница",
"position": "Позиция",
"layout": "Раскладка",
"autoLayout": "Автораскладка",
"alignment": "Выравнивание",
"appearance": "Внешний вид",
"fill": "Заливка",
"stroke": "Обводка",
@ -98,6 +100,26 @@
"noLocalVariables": "Нет локальных переменных",
"openVariables": "Открыть переменные",
"addPage": "Добавить страницу",
"toggleVisibility": "Переключить видимость",
"independentCornerRadii": "Независимые радиусы углов",
"detachVariable": "Отвязать переменную",
"applyVariable": "Применить переменную",
"noVariablesFound": "Переменные не найдены",
"addAutoLayout": "Добавить автораскладку (Shift+A)",
"removeAutoLayout": "Убрать автораскладку",
"alignLeft": "Выровнять по левому краю",
"alignCenterHorizontally": "Выровнять по центру по горизонтали",
"alignRight": "Выровнять по правому краю",
"alignTop": "Выровнять по верхнему краю",
"alignCenterVertically": "Выровнять по центру по вертикали",
"alignBottom": "Выровнять по нижнему краю",
"flipHorizontal": "Отразить по горизонтали",
"flipVertical": "Отразить по вертикали",
"rotate90": "Повернуть на 90°",
"mixedFillsHelp": "Нажмите +, чтобы заменить смешанные заливки",
"mixedStrokesHelp": "Нажмите +, чтобы заменить смешанные обводки",
"mixedEffectsHelp": "Нажмите +, чтобы заменить смешанные эффекты",
"strokeSides": "Стороны обводки",
"mixed": "Смешанное",
"layersCount": "{count} слоёв",
"goToMainComponent": "Перейти к главному компоненту",
@ -106,6 +128,8 @@
"linearGradient": "Линейный",
"radialGradient": "Радиальный",
"image": "Изображение",
"stops": "Точки",
"addStop": "Добавить точку",
"alignLeft": "По левому краю",
"alignCenter": "По центру",
"alignRight": "По правому краю",
@ -138,6 +162,8 @@
"describeCreateOrChange": "Опишите, что вы хотите создать или изменить.",
"stopGenerating": "Остановить генерацию",
"sendMessage": "Отправить сообщение",
"baseURLPlaceholder": "Base URL (например, http://localhost:11434/v1)",
"modelIDPlaceholder": "Model ID (например, llama-3.3-70b)",
"search": "Поиск…",
"noResults": "Ничего не найдено",
"share": "Поделиться"

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { twMerge } from 'tailwind-merge'
import { FillPickerRoot } from '@open-pencil/vue'
import { FillPickerRoot, useI18n } from '@open-pencil/vue'
import GradientEditor from './GradientEditor.vue'
import HsvColorArea from './HsvColorArea.vue'
@ -24,6 +24,7 @@ function tabClass(active: boolean) {
const { fill } = defineProps<{ fill: Fill }>()
const emit = defineEmits<{ update: [fill: Fill] }>()
const cls = usePopoverUI({ content: 'w-60 p-2' })
const { panels } = useI18n()
</script>
<template>
@ -35,7 +36,7 @@ const cls = usePopoverUI({ content: 'w-60 p-2' })
>
<template #default="{ fill: currentFill, category, toSolid, toGradient, toImage, update }">
<div class="mb-2 flex items-center gap-0.5">
<Tip label="Solid">
<Tip :label="panels.solid">
<button
:class="tabClass(category === 'SOLID')"
data-test-id="fill-picker-tab-solid"
@ -44,7 +45,7 @@ const cls = usePopoverUI({ content: 'w-60 p-2' })
<icon-lucide-square class="size-3.5" />
</button>
</Tip>
<Tip label="Gradient">
<Tip :label="panels.linearGradient">
<button
:class="tabClass(category === 'GRADIENT')"
data-test-id="fill-picker-tab-gradient"
@ -53,7 +54,7 @@ const cls = usePopoverUI({ content: 'w-60 p-2' })
<icon-lucide-blend class="size-3.5" />
</button>
</Tip>
<Tip label="Image">
<Tip :label="panels.image">
<button
:class="tabClass(category === 'IMAGE')"
data-test-id="fill-picker-tab-image"

View file

@ -4,12 +4,13 @@ import Tip from './ui/Tip.vue'
import HsvColorArea from './HsvColorArea.vue'
import ScrubInput from './ScrubInput.vue'
import { colorToCSS } from '@open-pencil/core'
import { GradientEditorRoot, GradientEditorBar, GradientEditorStop } from '@open-pencil/vue'
import { GradientEditorRoot, GradientEditorBar, GradientEditorStop, useI18n } from '@open-pencil/vue'
import type { Fill } from '@open-pencil/core'
const { fill } = defineProps<{ fill: Fill }>()
const emit = defineEmits<{ update: [fill: Fill] }>()
const { panels } = useI18n()
</script>
<template>
@ -52,8 +53,8 @@ const emit = defineEmits<{ update: [fill: Fill] }>()
<div class="mb-2">
<div class="mb-1 flex items-center justify-between">
<span class="text-[11px] text-muted">Stops</span>
<Tip label="Add stop">
<span class="text-[11px] text-muted">{{ panels.stops }}</span>
<Tip :label="panels.addStop">
<button
class="flex size-4 cursor-pointer items-center justify-center rounded border-none bg-transparent p-0 text-muted hover:text-surface"
data-test-id="fill-picker-add-stop"

View file

@ -49,7 +49,7 @@ function save() {
v-model="baseURLInput"
type="text"
data-test-id="provider-base-url"
placeholder="Base URL (e.g. http://localhost:11434/v1)"
:placeholder="dialogs.baseURLPlaceholder"
:class="uiInput()"
/>
@ -59,7 +59,7 @@ function save() {
v-model="customModelInput"
type="text"
data-test-id="provider-custom-model"
placeholder="Model ID (e.g. llama-3.3-70b)"
:placeholder="dialogs.modelIDPlaceholder"
:class="uiInput()"
/>

View file

@ -1,9 +1,11 @@
<script setup lang="ts">
import { AppearanceControlsRoot } from '@open-pencil/vue'
import { AppearanceControlsRoot, useI18n } from '@open-pencil/vue'
import ScrubInput from '@/components/ScrubInput.vue'
import Tip from '@/components/ui/Tip.vue'
import { sectionWrapper } from '@/components/ui/section'
const { panels } = useI18n()
</script>
<template>
@ -27,8 +29,8 @@ import { sectionWrapper } from '@/components/ui/section'
>
<div v-if="active" data-test-id="appearance-section" :class="sectionWrapper()">
<div class="mb-1.5 flex items-center justify-between">
<label class="text-[11px] text-muted">Appearance</label>
<Tip label="Toggle visibility">
<label class="text-[11px] text-muted">{{ panels.appearance }}</label>
<Tip :label="panels.toggleVisibility">
<button
data-test-id="appearance-visibility"
class="flex cursor-pointer items-center justify-center rounded border-none bg-transparent p-0.5 text-muted hover:bg-hover hover:text-surface"
@ -67,7 +69,7 @@ import { sectionWrapper } from '@/components/ui/section'
<icon-lucide-radius class="size-3" />
</template>
</ScrubInput>
<Tip label="Independent corner radii">
<Tip :label="panels.independentCornerRadii">
<button
data-test-id="independent-corners-toggle"
class="flex size-[26px] shrink-0 cursor-pointer items-center justify-center rounded border border-border bg-input text-muted hover:bg-hover hover:text-surface"

View file

@ -4,24 +4,25 @@ import ColorInput from '@/components/ColorInput.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import { iconButton } from '@/components/ui/icon-button'
import { sectionLabel, sectionWrapper } from '@/components/ui/section'
import { PropertyListRoot, useEffectsControls } from '@open-pencil/vue'
import { PropertyListRoot, useEffectsControls, useI18n } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core'
import type { Effect } from '@open-pencil/core'
const effectsCtx = useEffectsControls()
const { panels } = useI18n()
</script>
<template>
<PropertyListRoot
v-slot="{ items, isMixed, activeNode, patch, add, remove, toggleVisibility }"
prop-key="effects"
label="Effects"
:label="panels.effects"
>
<div data-test-id="effects-section" :class="sectionWrapper()">
<div class="flex items-center justify-between">
<label :class="sectionLabel()">Effects</label>
<label :class="sectionLabel()">{{ panels.effects }}</label>
<button
data-test-id="effects-section-add"
:class="iconButton()"
@ -31,7 +32,7 @@ const effectsCtx = useEffectsControls()
</button>
</div>
<p v-if="isMixed" class="text-[11px] text-muted">Click + to replace mixed effects</p>
<p v-if="isMixed" class="text-[11px] text-muted">{{ panels.mixedEffectsHelp }}</p>
<div
v-for="(effect, i) in items as Effect[]"

View file

@ -5,11 +5,12 @@ import AppSelect from '@/components/ui/AppSelect.vue'
import { iconButton } from '@/components/ui/icon-button'
import { sectionLabel, sectionWrapper } from '@/components/ui/section'
import { useEditorStore } from '@/stores/editor'
import { useExport } from '@open-pencil/vue'
import { useExport, useI18n } from '@open-pencil/vue'
import type { ExportFormat } from '@open-pencil/core'
const editorStore = useEditorStore()
const { panels } = useI18n()
const { settings, nodeName, addSetting, removeSetting, updateScale, updateFormat } = useExport()
const SCALE_OPTIONS = [0.5, 0.75, 1, 1.5, 2, 3, 4].map((s) => ({ value: s, label: `${s}x` }))
@ -72,7 +73,7 @@ onScopeDispose(() => {
<template>
<div data-test-id="export-section" :class="sectionWrapper()">
<div class="flex items-center justify-between">
<label :class="sectionLabel()">Export</label>
<label :class="sectionLabel()">{{ panels.export }}</label>
<button data-test-id="export-section-add" :class="iconButton()" @click="addSetting">+</button>
</div>

View file

@ -12,7 +12,7 @@ import {
} from 'reka-ui'
import { colorToCSS, colorToHexRaw } from '@open-pencil/core'
import { PropertyListRoot, useFillControls } from '@open-pencil/vue'
import { PropertyListRoot, useFillControls, useI18n } from '@open-pencil/vue'
import FillPicker from '@/components/FillPicker.vue'
import ScrubInput from '@/components/ScrubInput.vue'
@ -23,17 +23,18 @@ import { sectionLabel, sectionWrapper } from '@/components/ui/section'
import type { Fill, Variable } from '@open-pencil/core'
const fillCtx = useFillControls()
const { panels, dialogs } = useI18n()
</script>
<template>
<PropertyListRoot
v-slot="{ items, isMixed, activeNode, add, remove, update, patch, toggleVisibility }"
prop-key="fills"
label="Fill"
:label="panels.fill"
>
<div data-test-id="fill-section" :class="sectionWrapper()">
<div class="flex items-center justify-between">
<label :class="sectionLabel()">Fill</label>
<label :class="sectionLabel()">{{ panels.fill }}</label>
<button
data-test-id="fill-section-add"
:class="iconButton()"
@ -42,7 +43,7 @@ const fillCtx = useFillControls()
+
</button>
</div>
<p v-if="isMixed" class="text-[11px] text-muted">Click + to replace mixed fills</p>
<p v-if="isMixed" class="text-[11px] text-muted">{{ panels.mixedFillsHelp }}</p>
<div
v-for="(fill, i) in items as Fill[]"
:key="i"
@ -58,7 +59,7 @@ const fillCtx = useFillControls()
>
{{ fillCtx.getBoundVariable(activeNode.id, i)!.name }}
</span>
<Tip label="Detach variable">
<Tip :label="panels.detachVariable">
<button
data-test-id="fill-unbind-variable"
class="cursor-pointer border-none bg-transparent p-0 text-violet-400 hover:text-surface"
@ -95,7 +96,7 @@ const fillCtx = useFillControls()
!fillCtx.getBoundVariable(activeNode.id, i)
"
>
<Tip label="Apply variable">
<Tip :label="panels.applyVariable">
<PopoverTrigger
class="cursor-pointer border-none bg-transparent p-0 text-muted hover:text-surface"
>
@ -115,13 +116,13 @@ const fillCtx = useFillControls()
>
<ComboboxInput
:model-value="fillCtx.searchTerm.value"
placeholder="Search variables…"
:placeholder="dialogs.search"
class="w-full border-b border-border bg-transparent px-2 py-1.5 text-[11px] text-surface outline-none placeholder:text-muted"
@update:model-value="fillCtx.searchTerm.value = String($event)"
/>
<ComboboxContent class="max-h-48 overflow-y-auto p-1">
<ComboboxEmpty class="px-2 py-3 text-center text-[11px] text-muted"
>No variables found</ComboboxEmpty
>{{ panels.noVariablesFound }}</ComboboxEmpty
>
<ComboboxItem
v-for="v in fillCtx.filteredVariables.value"

View file

@ -1,17 +1,19 @@
<script setup lang="ts">
import { LayoutControlsRoot } from '@open-pencil/vue'
import { LayoutControlsRoot, useI18n } from '@open-pencil/vue'
import AppSelect from '@/components/ui/AppSelect.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import Tip from '@/components/ui/Tip.vue'
import { sectionWrapper } from '@/components/ui/section'
const { panels } = useI18n()
</script>
<template>
<LayoutControlsRoot v-slot="ctx">
<template v-if="ctx.node">
<div data-test-id="layout-section" :class="sectionWrapper()">
<label class="mb-1.5 block text-[11px] text-muted">Layout</label>
<label class="mb-1.5 block text-[11px] text-muted">{{ panels.layout }}</label>
<div class="flex gap-1.5">
<div class="flex min-w-0 flex-1 items-center gap-1">
<ScrubInput
@ -49,8 +51,8 @@ import { sectionWrapper } from '@/components/ui/section'
<template v-if="ctx.node.type === 'FRAME'">
<div :class="sectionWrapper()">
<div class="flex items-center justify-between">
<label class="mb-1.5 block text-[11px] text-muted">Auto layout</label>
<Tip v-if="ctx.node.layoutMode === 'NONE'" label="Add auto layout (Shift+A)">
<label class="mb-1.5 block text-[11px] text-muted">{{ panels.autoLayout }}</label>
<Tip v-if="ctx.node.layoutMode === 'NONE'" :label="panels.addAutoLayout">
<button
class="cursor-pointer rounded border-none bg-transparent px-1 text-base leading-none text-muted hover:bg-hover hover:text-surface"
data-test-id="layout-add-auto"
@ -59,7 +61,7 @@ import { sectionWrapper } from '@/components/ui/section'
+
</button>
</Tip>
<Tip v-else label="Remove auto layout">
<Tip v-else :label="panels.removeAutoLayout">
<button
class="cursor-pointer rounded border-none bg-transparent px-1 text-base leading-none text-muted hover:bg-hover hover:text-surface"
data-test-id="layout-remove-auto"
@ -238,7 +240,7 @@ import { sectionWrapper } from '@/components/ui/section'
</template>
<div v-if="ctx.isFlex" class="mt-2">
<label class="mb-1 block text-[11px] text-muted">Alignment</label>
<label class="mb-1 block text-[11px] text-muted">{{ panels.alignment }}</label>
<div data-test-id="layout-alignment-grid" class="grid w-fit grid-cols-3 gap-0.5">
<button
v-for="cell in ctx.alignGrid"

View file

@ -3,7 +3,9 @@ import ScrubInput from '@/components/ScrubInput.vue'
import Tip from '@/components/ui/Tip.vue'
import { iconButton } from '@/components/ui/icon-button'
import { sectionWrapper } from '@/components/ui/section'
import { PositionControlsRoot } from '@open-pencil/vue'
import { PositionControlsRoot, useI18n } from '@open-pencil/vue'
const { panels } = useI18n()
</script>
<template>
@ -24,11 +26,11 @@ import { PositionControlsRoot } from '@open-pencil/vue'
}"
>
<div v-if="active" data-test-id="position-section" :class="sectionWrapper()">
<label class="mb-1.5 block text-[11px] text-muted">Position</label>
<label class="mb-1.5 block text-[11px] text-muted">{{ panels.position }}</label>
<div class="mb-1.5 flex gap-2">
<div class="flex gap-0.5">
<Tip label="Align left">
<Tip :label="panels.alignLeft">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-left"
@ -37,7 +39,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-align-horizontal-justify-start class="size-3.5" />
</button>
</Tip>
<Tip label="Align center horizontally">
<Tip :label="panels.alignCenterHorizontally">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-center-h"
@ -46,7 +48,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-align-horizontal-justify-center class="size-3.5" />
</button>
</Tip>
<Tip label="Align right">
<Tip :label="panels.alignRight">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-right"
@ -57,7 +59,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
</Tip>
</div>
<div class="flex gap-0.5">
<Tip label="Align top">
<Tip :label="panels.alignTop">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-top"
@ -66,7 +68,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-align-vertical-justify-start class="size-3.5" />
</button>
</Tip>
<Tip label="Align center vertically">
<Tip :label="panels.alignCenterVertically">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-center-v"
@ -75,7 +77,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-align-vertical-justify-center class="size-3.5" />
</button>
</Tip>
<Tip label="Align bottom">
<Tip :label="panels.alignBottom">
<button
:class="iconButton({ size: 'md' })"
data-test-id="position-align-bottom"
@ -133,7 +135,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-rotate-ccw class="size-3" />
</template>
</ScrubInput>
<Tip label="Flip horizontal">
<Tip :label="panels.flipHorizontal">
<button
:class="iconButton({ size: 'md', ui: { base: 'shrink-0' } })"
data-test-id="position-flip-horizontal"
@ -142,7 +144,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-flip-horizontal class="size-3.5" />
</button>
</Tip>
<Tip label="Flip vertical">
<Tip :label="panels.flipVertical">
<button
:class="iconButton({ size: 'md', ui: { base: 'shrink-0' } })"
data-test-id="position-flip-vertical"
@ -151,7 +153,7 @@ import { PositionControlsRoot } from '@open-pencil/vue'
<icon-lucide-flip-vertical class="size-3.5" />
</button>
</Tip>
<Tip label="Rotate 90°">
<Tip :label="panels.rotate90">
<button
:class="iconButton({ size: 'md', ui: { base: 'shrink-0' } })"
data-test-id="position-rotate-90"

View file

@ -7,7 +7,7 @@ import {
DropdownMenuTrigger
} from 'reka-ui'
import { PropertyListRoot, useStrokeControls } from '@open-pencil/vue'
import { PropertyListRoot, useStrokeControls, useI18n } from '@open-pencil/vue'
import AppSelect from '@/components/ui/AppSelect.vue'
import ColorInput from '@/components/ColorInput.vue'
@ -20,6 +20,7 @@ import { sectionLabel, sectionWrapper } from '@/components/ui/section'
import type { SceneNode, Stroke } from '@open-pencil/core'
const strokeCtx = useStrokeControls()
const { panels } = useI18n()
const sideMenuCls = useMenuUI({
content: 'min-w-[140px] rounded-md p-0.5',
item: 'relative px-2'
@ -30,11 +31,11 @@ const sideMenuCls = useMenuUI({
<PropertyListRoot
v-slot="{ items, isMixed, activeNode, add, remove, patch, toggleVisibility }"
prop-key="strokes"
label="Stroke"
:label="panels.stroke"
>
<div data-test-id="stroke-section" :class="sectionWrapper()">
<div class="flex items-center justify-between">
<label :class="sectionLabel()">Stroke</label>
<label :class="sectionLabel()">{{ panels.stroke }}</label>
<button
data-test-id="stroke-section-add"
:class="iconButton()"
@ -44,7 +45,7 @@ const sideMenuCls = useMenuUI({
</button>
</div>
<p v-if="isMixed" class="text-[11px] text-muted">Click + to replace mixed strokes</p>
<p v-if="isMixed" class="text-[11px] text-muted">{{ panels.mixedStrokesHelp }}</p>
<div
v-for="(stroke, i) in items as Stroke[]"
@ -101,7 +102,7 @@ const sideMenuCls = useMenuUI({
</template>
</ScrubInput>
<DropdownMenuRoot v-model:open="strokeCtx.sideMenuOpen.value">
<Tip label="Stroke sides">
<Tip :label="panels.strokeSides">
<DropdownMenuTrigger as-child>
<button
class="flex size-[26px] shrink-0 cursor-pointer items-center justify-center rounded border border-border bg-input text-muted hover:bg-hover hover:text-surface"

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ToggleGroupItem, ToggleGroupRoot } from 'reka-ui'
import { TypographyControlsRoot } from '@open-pencil/vue'
import { TypographyControlsRoot, useI18n } from '@open-pencil/vue'
import FontPicker from '@/components/FontPicker.vue'
import ScrubInput from '@/components/ScrubInput.vue'
@ -9,12 +9,14 @@ import AppSelect from '@/components/ui/AppSelect.vue'
import Tip from '@/components/ui/Tip.vue'
import { sectionWrapper } from '@/components/ui/section'
import { loadFont } from '@/engine/fonts'
const { panels } = useI18n()
</script>
<template>
<TypographyControlsRoot v-slot="ctx" :load-font="loadFont">
<div v-if="ctx.node.value" data-test-id="typography-section" :class="sectionWrapper()">
<label class="mb-1.5 block text-[11px] text-muted">Typography</label>
<label class="mb-1.5 block text-[11px] text-muted">{{ panels.typography }}</label>
<div class="mb-1.5 flex items-center gap-1.5">
<FontPicker