♻️ Name the shared CJK text options after CJK, not Japanese

The text options started with Japanese only. Chinese and Korean reused
its controls and helpers, which kept Japanese names, and some rules got
a copy per language.

- Rename text-japanese-layout to text-cjk-controls: the controls every
  language panel shares. The Japanese panel and its own controls move to
  text-language-layout, next to the Chinese and Korean ones.
- Share one option row, one writing mode row (languages add their radio
  groups to it), and one emphasis and clearance select per language.
- Rename the selection, plugin and HTML style helpers from japanese to
  cjk, `schema:jl-enum` to `schema:cjk-enum`, and the stored writing
  mode helpers to `stored-writing-mode` and `stored-vertical?`, apart
  from the rendered ones in writing-mode.
- Build the language label and dropdown from one table of languages,
  and rename the profile-* translation keys to language-*.
- Decide the attrs each language renders from one table per level
  (root, paragraph, span).
- Serialize korean-wrap through the CJK enum table and RawKoreanWrap.
- Compare the CJK option attrs in a loop in the text menu.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
alonso.torres 2026-11-05 16:07:45 +01:00
parent 619f136587
commit 2ac10ce6d7
23 changed files with 758 additions and 754 deletions

View File

@ -8,7 +8,11 @@
(:require
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.types.text :as txt]))
[app.common.types.text :as txt]
[app.common.types.text.cjk-layout :as cjk]))
(def ^:private language-paragraph-attrs
(set cjk/language-paragraph-attrs))
(defn- get-attr
[obj attr]
@ -42,9 +46,9 @@
:multiple)
(get obj attr ::unset)))
;; An absent language is the plain profile, so it must participate
;; when reporting mixed text selections.
(#{:text-language :korean-wrap} attr)
;; A text without language differs from one with a language, so an
;; absent value must count when reporting mixed text selections.
(contains? language-paragraph-attrs attr)
(get obj attr)
:else

View File

@ -49,6 +49,7 @@
["ruby-align" "RawRubyAlign"]
["ruby-overhang" "RawRubyOverhang"]
["ruby-side" "RawRubySide"]
["korean-wrap" "RawKoreanWrap"]
["line-adjustment" "RawLineAdjustment"]
["text-decoration" "RawTextDecoration"]
["text-transform" "RawTextTransform"]

View File

@ -118,6 +118,7 @@
text-transform (sr/translate-text-transform (get paragraph :text-transform))
writing-mode (sr/translate-text-layout-enum :writing-mode (get paragraph :writing-mode))
text-orientation (sr/translate-text-layout-enum :text-orientation (get paragraph :text-orientation))
korean-wrap (sr/translate-text-layout-enum :korean-wrap (get paragraph :korean-wrap))
line-height (serialize-line-height (get paragraph :line-height))
letter-spacing (serialize-letter-spacing (get paragraph :letter-spacing))]
(-> offset
@ -127,7 +128,7 @@
(mem/write-u8 dview text-transform)
(mem/write-u8 dview writing-mode)
(mem/write-u8 dview text-orientation)
(mem/write-u8 dview (case (:korean-wrap paragraph) "spaces-only" 1 "keep-words" 2 0))
(mem/write-u8 dview korean-wrap)
;; Alignment padding; must match RawParagraphData in Rust.
(mem/write-u8 dview 0)
(mem/write-f32 dview line-height)

View File

@ -16,9 +16,9 @@
(def node-types #{"root" "paragraph-set" "paragraph"})
;; Values of a Japanese layout enum attr; paragraphs set the orientation
;; Values of a CJK layout enum attr; paragraphs set the orientation
;; of the whole shape and spans carry a copy.
(defn- schema:jl-enum
(defn- schema:cjk-enum
[attr]
(into [:enum] (get cjk/enum-values attr)))
@ -31,7 +31,7 @@
[:map
[:type [:= "root"]]
[:key {:optional true} :string]
[:line-adjustment {:optional true} (schema:jl-enum :line-adjustment)]
[:line-adjustment {:optional true} (schema:cjk-enum :line-adjustment)]
[:children
[:vector {:min 1 :gen/max 2 :gen/min 1}
[:map
@ -52,9 +52,9 @@
;; Paragraph language selects typography; it never follows the UI locale.
[:text-language {:optional true} schema:text-language]
;; Word mode keeps punctuation rules; retain the old POC value on reload.
[:korean-wrap {:optional true} [:enum "syllable" "spaces-only" "keep-words"]]
[:writing-mode {:optional true} (schema:jl-enum :writing-mode)]
[:text-orientation {:optional true} (schema:jl-enum :text-orientation)]
[:korean-wrap {:optional true} (schema:cjk-enum :korean-wrap)]
[:writing-mode {:optional true} (schema:cjk-enum :writing-mode)]
[:text-orientation {:optional true} (schema:cjk-enum :text-orientation)]
[:text-decoration {:optional true} ::sm/text]
[:text-transform {:optional true} ::sm/text]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
@ -71,21 +71,21 @@
[:font-style {:optional true} ::sm/text]
[:font-weight {:optional true} ::sm/text]
[:direction {:optional true} ::sm/text]
[:text-combine-upright {:optional true} (schema:jl-enum :text-combine-upright)]
[:text-emphasis {:optional true} (schema:jl-enum :text-emphasis)]
[:text-emphasis-position {:optional true} (schema:jl-enum :text-emphasis-position)]
[:text-underline-position {:optional true} (schema:jl-enum :text-underline-position)]
[:text-combine-upright {:optional true} (schema:cjk-enum :text-combine-upright)]
[:text-emphasis {:optional true} (schema:cjk-enum :text-emphasis)]
[:text-emphasis-position {:optional true} (schema:cjk-enum :text-emphasis-position)]
[:text-underline-position {:optional true} (schema:cjk-enum :text-underline-position)]
[:ruby {:optional true} :string]
[:ruby-hidden {:optional true} :boolean]
[:ruby-zhuyin {:optional true} :boolean]
[:ruby-size {:optional true} (schema:jl-enum :ruby-size)]
[:ruby-align {:optional true} (schema:jl-enum :ruby-align)]
[:ruby-overhang {:optional true} (schema:jl-enum :ruby-overhang)]
[:ruby-side {:optional true} (schema:jl-enum :ruby-side)]
[:warichu {:optional true} (schema:jl-enum :warichu)]
[:font-features {:optional true} (schema:jl-enum :font-features)]
[:annotation-clearance {:optional true} (schema:jl-enum :annotation-clearance)]
[:text-orientation {:optional true} (schema:jl-enum :text-orientation)]
[:ruby-size {:optional true} (schema:cjk-enum :ruby-size)]
[:ruby-align {:optional true} (schema:cjk-enum :ruby-align)]
[:ruby-overhang {:optional true} (schema:cjk-enum :ruby-overhang)]
[:ruby-side {:optional true} (schema:cjk-enum :ruby-side)]
[:warichu {:optional true} (schema:cjk-enum :warichu)]
[:font-features {:optional true} (schema:cjk-enum :font-features)]
[:annotation-clearance {:optional true} (schema:cjk-enum :annotation-clearance)]
[:text-orientation {:optional true} (schema:cjk-enum :text-orientation)]
[:text-decoration {:optional true} ::sm/text]
[:text-transform {:optional true} ::sm/text]
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]

View File

@ -108,8 +108,8 @@
(d/concat-vec
text-align-attrs
text-direction-attrs
[:text-language :korean-wrap]
cjk/whole-shape-paragraph-attrs))
cjk/language-paragraph-attrs
cjk/vertical-writing-attrs))
(def text-node-attrs
(d/concat-vec

View File

@ -34,7 +34,10 @@
:ruby-side ["over" "under"]
;; Root attr: how lines slightly too long are fitted (Adobe's Kinsoku
;; Type). It decides squeezing; alignment decides the space left over.
:line-adjustment ["push-in-first" "push-out-first" "push-out-only"]})
:line-adjustment ["push-in-first" "push-out-first" "push-out-only"]
;; Korean line breaking: between syllables, or between words only.
;; "spaces-only" is the former name of "keep-words", kept on reload.
:korean-wrap ["syllable" "spaces-only" "keep-words"]})
(def ^:private enum-value-sets
(update-vals enum-values set))
@ -49,15 +52,20 @@
[attr value]
(contains? (get enum-value-sets attr) value))
;; Paragraph attrs. They apply to the whole shape: every paragraph carries
;; the first one's value.
(def whole-shape-paragraph-attrs
;; Vertical writing paragraph attrs. They apply to the whole shape: every
;; paragraph carries the first one's value.
(def vertical-writing-attrs
[:writing-mode :text-orientation])
;; Paragraph attrs the text options write to every paragraph, whatever the
;; editor selection: a text has one writing mode and one language.
(def whole-text-paragraph-attrs
(conj whole-shape-paragraph-attrs :text-language))
(conj vertical-writing-attrs :text-language))
;; Paragraph attrs of the language. A text has one language, but Korean
;; wrapping can change per paragraph.
(def language-paragraph-attrs
[:text-language :korean-wrap])
;; Ruby attrs that change how a reading looks, not the reading itself.
(def ruby-presentation-attrs
@ -83,7 +91,7 @@
;; Values of the paragraph attrs when a paragraph does not store them.
(def paragraph-attr-defaults
(into {} (map (juxt identity enum-default)) whole-shape-paragraph-attrs))
(into {} (map (juxt identity enum-default)) vertical-writing-attrs))
;; Glyph of each emphasis style, per CSS `text-emphasis-style`.
(def ^:private emphasis-mark-chars
@ -210,21 +218,23 @@
(>= (count text) 2))))
(defn whole-shape-attr
"Value of a whole-shape paragraph `attr` (see `whole-shape-paragraph-attrs`)
"Value of a whole-shape paragraph `attr` (see `whole-text-paragraph-attrs`)
in a text content: stored per paragraph, the first paragraph's value
decides. Nil when unset."
[content attr]
(dm/get-in content [:children 0 :children 0 attr]))
(defn content-writing-mode
"Writing mode of a text content, or nil when unset."
(defn stored-writing-mode
"Writing mode stored in a text content, whatever its language, or nil
when unset. `active-writing-mode` is the one the renderer uses."
[content]
(whole-shape-attr content :writing-mode))
(defn vertical-text-content?
"True when the text content flows vertically (vertical-rl)."
(defn stored-vertical?
"True when the text content stores vertical writing (vertical-rl),
whatever its language."
[content]
(= "vertical-rl" (content-writing-mode content)))
(= "vertical-rl" (stored-writing-mode content)))
;; --- Active language
@ -239,16 +249,31 @@
"ko" :korean
nil)))
;; Span attrs each language renders; the others stay in the content and come
(def ^:private ruby-attrs
(into #{:ruby} ruby-presentation-attrs))
;; CJK attrs each language renders; the others stay in the content and come
;; back when the text takes that language again.
(def ^:private language-span-attrs
{:japanese #{:text-combine-upright :warichu :text-emphasis :annotation-clearance
:ruby :ruby-hidden :ruby-size :ruby-align :ruby-overhang :ruby-side
:font-features}
:chinese #{:text-emphasis :text-emphasis-position :text-underline-position
:annotation-clearance :ruby :ruby-zhuyin :ruby-hidden :ruby-size
:ruby-align :ruby-overhang :ruby-side}
:korean #{}})
(def ^:private language-attrs
{:japanese {:root #{:line-adjustment}
:paragraph (set vertical-writing-attrs)
:span (into #{:text-combine-upright :warichu :text-emphasis
:annotation-clearance :font-features}
(disj ruby-attrs :ruby-zhuyin))}
:chinese {:root #{}
:paragraph (set vertical-writing-attrs)
:span (into #{:text-emphasis :text-emphasis-position
:text-underline-position :annotation-clearance}
ruby-attrs)}
:korean {:root #{}
:paragraph (conj (set vertical-writing-attrs) :korean-wrap)
:span #{}}})
;; Attrs some language renders, per level: the ones without language drop.
(def ^:private language-dependent-attrs
(reduce-kv (fn [result _ levels] (merge-with into result levels))
{:root #{} :paragraph #{} :span #{}}
language-attrs))
(defn content-language
"Language of a text content: the first paragraph's `:text-language`, as a
@ -256,30 +281,29 @@
[content]
(whole-shape-attr content :text-language))
(defn- active-span
[span family]
(let [kept (get language-span-attrs family #{})]
(reduce (fn [span attr]
(if (contains? kept attr) span (dissoc span attr)))
span
span-attrs)))
(defn- keep-language-attrs
"`node` without the CJK attrs that its language `family` does not render
at `level` (`:root`, `:paragraph` or `:span`)."
[node family level]
(let [kept (get-in language-attrs [family level] #{})]
(reduce (fn [node attr]
(if (contains? kept attr) node (dissoc node attr)))
node
(get language-dependent-attrs level))))
(defn- active-paragraph
[paragraph default-language]
(let [language (or (:text-language paragraph) default-language)
family (language-family language)]
(cond-> (d/update-when paragraph :children (partial mapv #(active-span % family)))
(cond-> (-> paragraph
(d/update-when :children
(partial mapv #(keep-language-attrs % family :span)))
(keep-language-attrs family :paragraph))
(some? language)
(assoc :text-language language)
(nil? family)
(d/without-keys whole-shape-paragraph-attrs)
(and (nil? family) (= "distribute" (:text-align paragraph)))
(assoc :text-align "left")
(not= :korean family)
(dissoc :korean-wrap))))
(assoc :text-align "left"))))
(defn active-content
"Text `content` as the renderer lays it out. A paragraph without language
@ -288,17 +312,17 @@
horizontal and not distributed. The stored content keeps every attr."
[content default-language]
(let [family (language-family (or (content-language content) default-language))]
(cond-> (d/update-when content :children
(partial mapv
(fn [paragraph-set]
(d/update-when paragraph-set :children
(partial mapv #(active-paragraph % default-language))))))
(not= :japanese family)
(dissoc :line-adjustment))))
(-> content
(d/update-when :children
(partial mapv
(fn [paragraph-set]
(d/update-when paragraph-set :children
(partial mapv #(active-paragraph % default-language))))))
(keep-language-attrs family :root))))
(defn active-writing-mode
"Writing mode the renderer lays `content` out with (see `active-content`):
nil when unset or when the text has no CJK language."
[content default-language]
(when (some? (language-family (or (content-language content) default-language)))
(content-writing-mode content)))
(stored-writing-mode content)))

View File

@ -397,7 +397,7 @@
{:korean-wrap "keep-all"}]]
(t/is (not (ctst/valid-content? (with-paragraph-attrs attrs))) (pr-str attrs)))
(t/is (contains? (set cttx/paragraph-attrs) :text-language))
(t/is (not (contains? (set cjk/whole-shape-paragraph-attrs) :text-language))))
(t/is (not (contains? (set cjk/vertical-writing-attrs) :text-language))))
(t/deftest japanese-span-attrs-accept-supported-values
(t/is (ctst/valid-content?

View File

@ -26,7 +26,7 @@
(generate-root-styles props node false))
([{:keys [width height]} node code?]
(let [valign (:vertical-align node "top")
writing-mode (cjk/content-writing-mode node)
writing-mode (cjk/stored-writing-mode node)
base #js {:height (when-not code? (fmt/format-pixels height))
:width (when-not code? (fmt/format-pixels width))
:display "flex"
@ -138,7 +138,7 @@
(when (pos? room)
(str "calc(" base " + " (fmt/format-number room) ")")))))
(defn- add-japanese-text-styles!
(defn- add-cjk-text-styles!
[style data]
(let [text-combine-upright (:text-combine-upright data)
text-emphasis (:text-emphasis data)
@ -271,7 +271,7 @@
(obj/set! "whiteSpace" "pre")
:always
(add-japanese-text-styles! data)))))
(add-cjk-text-styles! data)))))
(defn- ruby-font-size
"Pixel size of a span's ruby text. Paragraphs set font-size 0, so a
@ -299,7 +299,7 @@
(obj/unset! "inlineSize")))
(defn- enum-or-default
"Value of the Japanese layout enum `attr` in `data`, or its default when
"Value of the CJK layout enum `attr` in `data`, or its default when
unset or unknown."
[data attr]
(let [value (get data attr)]

View File

@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.types.text :as txt]
[app.common.types.text.cjk-layout :as cjk]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.workspace.libraries :as dwl]
@ -266,6 +267,14 @@
(->> (:typography tokens)
(d/seek #(= (:id %) (uuid/uuid id)))))
;; CJK attrs the text options show: language, writing mode, readings and
;; the other span, paragraph and root attrs.
(def ^:private cjk-option-attrs
(d/concat-vec cjk/language-paragraph-attrs
cjk/vertical-writing-attrs
cjk/span-attrs
[:line-adjustment]))
(defn- check-props [n-props o-props]
(let [o-values (unchecked-get o-props "values")
n-values (unchecked-get n-props "values")
@ -314,42 +323,6 @@
(get n-values :text-decoration))
(identical? (get o-values :text-direction)
(get n-values :text-direction))
(identical? (get o-values :text-language)
(get n-values :text-language))
(identical? (get o-values :korean-wrap)
(get n-values :korean-wrap))
(identical? (get o-values :writing-mode)
(get n-values :writing-mode))
(identical? (get o-values :text-orientation)
(get n-values :text-orientation))
(identical? (get o-values :text-combine-upright)
(get n-values :text-combine-upright))
(identical? (get o-values :warichu)
(get n-values :warichu))
(identical? (get o-values :font-features)
(get n-values :font-features))
(identical? (get o-values :annotation-clearance)
(get n-values :annotation-clearance))
(identical? (get o-values :text-emphasis)
(get n-values :text-emphasis))
(identical? (get o-values :text-emphasis-position)
(get n-values :text-emphasis-position))
(identical? (get o-values :text-underline-position)
(get n-values :text-underline-position))
(identical? (get o-values :ruby)
(get n-values :ruby))
(identical? (get o-values :ruby-zhuyin)
(get n-values :ruby-zhuyin))
(identical? (get o-values :ruby-hidden)
(get n-values :ruby-hidden))
(identical? (get o-values :ruby-size)
(get n-values :ruby-size))
(identical? (get o-values :ruby-align)
(get n-values :ruby-align))
(identical? (get o-values :ruby-overhang)
(get n-values :ruby-overhang))
(identical? (get o-values :ruby-side)
(get n-values :ruby-side))
(identical? (get o-values :text-transform)
(get n-values :text-transform))
(identical? (get o-values :typography-ref-file)
@ -358,8 +331,7 @@
(get n-values :typography-ref-id))
(identical? (get o-values :vertical-align)
(get n-values :vertical-align))
(identical? (get o-values :line-adjustment)
(get n-values :line-adjustment)))))
(every? #(identical? (get o-values %) (get n-values %)) cjk-option-attrs))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Main component

View File

@ -4,7 +4,10 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.main.ui.workspace.sidebar.options.menus.text-japanese-layout
(ns app.main.ui.workspace.sidebar.options.menus.text-cjk-controls
"Controls the CJK language panels share: writing mode, readings (ruby),
emphasis marks and annotation positions. The panels of each language
are in `text-language-layout`."
(:require-macros [app.main.style :as stl])
(:require
[app.common.types.text.cjk-layout :as cjk]
@ -23,7 +26,7 @@
(def ^:private mixed-span-values
#{:mixed :multiple "mixed" "multiple"})
(defn- mixed-span-value?
(defn mixed-span-value?
[value]
(contains? mixed-span-values value))
@ -48,41 +51,26 @@
:disabled true
:dimmed true})))
(defn vertical-japanese-layout?
"True when the Japanese layout controls are editing vertical text."
(defn vertical-writing?
"True when the option `values` are of vertical text. The panels only
show under a CJK language, so the writing mode alone decides."
[values]
(= "vertical-rl" (:writing-mode values)))
(defn proportional-metrics-feature
"Proportional metrics feature for the writing mode: `vpal` or `palt`."
[writing-mode]
(if (= writing-mode "vertical-rl") "vpal" "palt"))
(defn text-combine-upright-options
[text-selection-active translate]
(cond-> [{:value "none"
:id "none-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-none")
:icon i/text-combine-upright-none}]
text-selection-active
(conj {:value "all"
:id "all-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-all")
:icon i/text-combine-upright-all})
true
(conj {:value "digits"
:id "digits-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-digits")
:icon i/text-combine-upright-digits})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Sub-components
;; Generic controls
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc option-row*
"Row of a language panel: a `label` and the control in `children`."
[{:keys [label children]}]
[:div {:class (stl/css :option-row)}
[:span {:class (stl/css :option-label)} label]
children])
(mf/defc attr-radio-options*
"Radio group bound to one text attribute."
[{:keys [values on-change on-blur attr default-value name class options]}]
[{:keys [values on-change on-blur attr default-value name options]}]
(let [selected (radio-selected (get values attr) default-value)
group-id (mf/use-id)
options (mf/with-memo [options group-id]
@ -94,12 +82,34 @@
(fn [value]
(on-change {attr value})
(when (some? on-blur) (on-blur))))]
[:div {:class class}
[:div {:class (stl/css :radio-options)}
[:> radio-buttons* {:selected selected
:on-change handle-change
:name name
:options options}]]))
(mf/defc span-select-option*
"Select bound to one span attribute, with a disabled entry for mixed values."
[{:keys [values on-change on-blur attr default-value label options]}]
(let [value (get values attr)
selected (span-select-value value default-value)
options (with-mixed-span-option options value)
handle-change
(mf/use-fn
(mf/deps attr on-change on-blur)
(fn [value]
(on-change {attr value})
(when (some? on-blur) (on-blur))))]
[:> option-row* {:label label}
[:> select* {:default-selected selected
:aria-label label
:options options
:on-change handle-change}]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Writing mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn writing-mode-options
[translate]
[{:value "horizontal-tb"
@ -123,65 +133,32 @@
:icon i/text-orientation-upright}])
(mf/defc writing-mode-controls*
[{:keys [values on-change on-blur]}]
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur})]
[:div {:class (stl/css :japanese-icon-options)}
"Writing mode row, with the orientation for vertical text. `children` add
the radio groups of a language to the row."
[{:keys [values on-change on-blur children]}]
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur})
options (mf/with-memo []
{:writing-mode (writing-mode-options tr)
:text-orientation (text-orientation-options tr)})]
[:div {:class (stl/css :icon-options)}
[:> attr-radio-options* (mf/spread-props props
{:attr :writing-mode
:default-value (cjk/enum-default :writing-mode)
:name "writing-mode-options"
:class (stl/css :writing-mode-options)
:options (writing-mode-options tr)})]
(when (vertical-japanese-layout? values)
:options (:writing-mode options)})]
;; An unset orientation reads as nil, or "" in the v2 editor; both
;; select "mixed".
(when (vertical-writing? values)
[:> attr-radio-options* (mf/spread-props props
{:attr :text-orientation
:default-value (cjk/enum-default :text-orientation)
:name "text-orientation-options"
:class (stl/css :text-orientation-options)
:options (text-orientation-options tr)})])]))
:options (:text-orientation options)})])
children]))
;; Warichu (割注): renders the selection as two half-size lines in one inline
;; position (top/bottom horizontally, right/left vertically).
(defn warichu-options
[translate]
[{:value "none"
:id "none-warichu"
:label (translate "workspace.options.text-options.warichu-none")
:icon i/warichu-none}
{:value "warichu"
:id "warichu-warichu"
:label (translate "workspace.options.text-options.warichu")
:icon i/warichu}])
;; select* matches options by :id, so each id is the stored value.
(defn text-combine-upright-count-options
[translate]
[{:id "digits2"
:label (translate "workspace.options.text-options.text-combine-upright-digits-2")}
{:id "digits3"
:label (translate "workspace.options.text-options.text-combine-upright-digits-3")}
{:id "digits"
:label (translate "workspace.options.text-options.text-combine-upright-digits-4")}])
(defn ruby-size-options
[translate]
[{:id "half" :label (translate "workspace.options.text-options.ruby-size-half")}
{:id "third" :label (translate "workspace.options.text-options.ruby-size-third")}
{:id "quarter" :label (translate "workspace.options.text-options.ruby-size-quarter")}])
(defn ruby-align-options
[translate]
[{:id "space-around" :label (translate "workspace.options.text-options.ruby-align-space-around")}
{:id "center" :label (translate "workspace.options.text-options.ruby-align-center")}
{:id "start" :label (translate "workspace.options.text-options.ruby-align-start")}
{:id "space-between" :label (translate "workspace.options.text-options.ruby-align-space-between")}])
(defn ruby-overhang-options
[translate]
[{:id "auto" :label (translate "workspace.options.text-options.ruby-overhang-auto")}
{:id "none" :label (translate "workspace.options.text-options.ruby-overhang-none")}])
;; --- Positions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Positions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Annotation positions store both axes (over or under the line, right or
;; left of the column), but only one applies to a writing mode: above/below
@ -267,54 +244,108 @@
choice (when (not= "auto" choice) choice)]
(position-of-sides attr (if ^boolean vertical [block choice] [choice inline])))))
(mf/defc text-combine-upright-options*
;; Digit TCY finds eligible runs itself, so it applies to a whole shape;
;; `all` is offered only for a text selection.
[{:keys [values on-change on-blur text-selection-active]}]
(let [text-combine-upright (radio-selected (:text-combine-upright values)
(cjk/enum-default :text-combine-upright))
selected (if (cjk/digit-combine? text-combine-upright) "digits" text-combine-upright)
options
(mf/with-memo [text-selection-active]
(text-combine-upright-options text-selection-active tr))
(mf/defc position-select-option*
"Select of the position `attr` with the sides of the writing mode only."
[{:keys [values on-change on-blur attr vertical label]}]
(let [value (get values attr)
selected (span-select-value (position-choice attr vertical value) "auto")
options (mf/with-memo [attr vertical value]
(with-mixed-span-option (position-options attr vertical) value))
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [value]
(on-change {:text-combine-upright value})
(mf/deps attr vertical value on-change on-blur)
(fn [choice]
(on-change {attr (position-value attr vertical value choice)})
(when (some? on-blur) (on-blur))))]
[:> option-row* {:label label}
[:> select* {:default-selected selected
:aria-label label
:options options
:on-change handle-change}]]))
[:div {:class (stl/css :text-combine-upright-options)}
[:> radio-buttons* {:selected selected
:on-change handle-change
:name "text-combine-upright-options"
:options options}]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Emphasis marks and annotation clearance
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc text-combine-upright-count-options*
(defn text-emphasis-options
([]
(text-emphasis-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.text-emphasis-none")}
{:id "filled-dot"
:label (translate "workspace.options.text-options.text-emphasis-filled-dot")}
{:id "open-dot"
:label (translate "workspace.options.text-options.text-emphasis-open-dot")}
{:id "filled-circle"
:label (translate "workspace.options.text-options.text-emphasis-filled-circle")}
{:id "open-circle"
:label (translate "workspace.options.text-options.text-emphasis-open-circle")}
{:id "filled-sesame"
:label (translate "workspace.options.text-options.text-emphasis-filled-sesame")}
{:id "open-sesame"
:label (translate "workspace.options.text-options.text-emphasis-open-sesame")}]))
(defn annotation-clearance-options
([]
(annotation-clearance-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.annotation-clearance-none")}
{:id "auto"
:label (translate "workspace.options.text-options.annotation-clearance-auto")}]))
(mf/defc text-emphasis-option*
"Emphasis mark (圏点, 着重号) of the spans."
[{:keys [values on-change on-blur]}]
(let [text-combine-upright (radio-selected (:text-combine-upright values)
(cjk/enum-default :text-combine-upright))
digits? (cjk/digit-combine? text-combine-upright)
options (mf/with-memo [] (text-combine-upright-count-options tr))
(let [options (mf/with-memo [] (text-emphasis-options))]
[:> span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :text-emphasis
:default-value (cjk/enum-default :text-emphasis)
:label (tr "workspace.options.text-options.text-emphasis")
:options options}]))
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [value]
(on-change {:text-combine-upright value})
(when (some? on-blur) (on-blur))))]
(mf/defc annotation-clearance-option*
"Whether readings and emphasis marks add to the line height."
[{:keys [values on-change on-blur]}]
(let [options (mf/with-memo [] (annotation-clearance-options))]
[:> span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :annotation-clearance
:default-value (cjk/enum-default :annotation-clearance)
:label (tr "workspace.options.text-options.annotation-clearance")
:options options}]))
(when digits?
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)}
(tr "workspace.options.text-options.text-combine-upright-digits-count")]
[:> select* {:default-selected text-combine-upright
:aria-label (tr "workspace.options.text-options.text-combine-upright-digits-count")
:options options
:on-change handle-change}]])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Readings (ruby)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; A reading is furigana in Japanese and pinyin or zhuyin in Chinese. The
;; panel of each language passes its `labels`.
(defn ruby-size-options
[translate]
[{:id "half" :label (translate "workspace.options.text-options.ruby-size-half")}
{:id "third" :label (translate "workspace.options.text-options.ruby-size-third")}
{:id "quarter" :label (translate "workspace.options.text-options.ruby-size-quarter")}])
(defn ruby-align-options
[translate]
[{:id "space-around" :label (translate "workspace.options.text-options.ruby-align-space-around")}
{:id "center" :label (translate "workspace.options.text-options.ruby-align-center")}
{:id "start" :label (translate "workspace.options.text-options.ruby-align-start")}
{:id "space-between" :label (translate "workspace.options.text-options.ruby-align-space-between")}])
(defn ruby-overhang-options
[translate]
[{:id "auto" :label (translate "workspace.options.text-options.ruby-overhang-auto")}
{:id "none" :label (translate "workspace.options.text-options.ruby-overhang-none")}])
(mf/defc ruby-options*
"Input of the reading of the selected span."
[{:keys [values on-change on-blur advanced-open on-toggle-advanced labels]}]
(let [ruby (span-input-value (:ruby values))
ruby* (mf/use-state ruby)
@ -352,8 +383,8 @@
[:div {:class (stl/css :ruby-options)}
[:> input* {:class (stl/css :ruby-input)
:label (or (:input labels) (tr "workspace.options.text-options.ruby"))
:placeholder (or (:placeholder labels) (tr "workspace.options.text-options.ruby-placeholder"))
:label (:input labels)
:placeholder (:placeholder labels)
:value value
:on-change handle-change
:on-blur commit!
@ -363,50 +394,31 @@
(when on-toggle-advanced
[:> icon-button* {:variant "ghost"
:selected advanced-open
:aria-label (or (:advanced labels) (tr "workspace.options.text-options.ruby-advanced-options"))
:aria-label (:advanced labels)
:aria-expanded advanced-open
:data-testid "ruby-advanced-options-toggle"
:on-click on-toggle-advanced
:icon i/menu}])]))
(mf/defc span-select-option*
"Select bound to one span attribute, with a disabled entry for mixed values."
[{:keys [values on-change on-blur attr default-value label options]}]
(let [value (get values attr)
selected (span-select-value value default-value)
options (with-mixed-span-option options value)
(mf/defc ruby-hidden-option*
"Switch hiding the readings, unset when the spans differ."
[{:keys [values on-change on-blur]}]
(let [value (:ruby-hidden values)
enabled? (cond
(mixed-span-value? value) nil
(true? value) true
:else false)
label (tr "workspace.options.text-options.ruby-hidden")
handle-change
(mf/use-fn
(mf/deps attr on-change on-blur)
(fn [value]
(on-change {attr value})
(mf/deps on-change on-blur)
(fn [checked?]
(on-change {:ruby-hidden checked?})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> select* {:default-selected selected
:aria-label label
:options options
:on-change handle-change}]]))
(mf/defc position-select-option*
"Select of the position `attr` with the sides of the writing mode only."
[{:keys [values on-change on-blur attr vertical label]}]
(let [value (get values attr)
selected (span-select-value (position-choice attr vertical value) "auto")
options (mf/with-memo [attr vertical value]
(with-mixed-span-option (position-options attr vertical) value))
handle-change
(mf/use-fn
(mf/deps attr vertical value on-change on-blur)
(fn [choice]
(on-change {attr (position-value attr vertical value choice)})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> select* {:default-selected selected
:aria-label label
:options options
:on-change handle-change}]]))
[:> option-row* {:label label}
[:> switch* {:default-checked enabled?
:aria-label label
:on-change handle-change}]]))
(defn- ruby-common-props
[values on-change on-blur labels vertical]
@ -417,26 +429,6 @@
:labels labels
:vertical vertical}))
(mf/defc ruby-hidden-option*
[{:keys [values on-change on-blur]}]
(let [value (:ruby-hidden values)
enabled? (cond
(mixed-span-value? value) nil
(true? value) true
:else false)
label (tr "workspace.options.text-options.ruby-hidden")
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [checked?]
(on-change {:ruby-hidden checked?})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :ruby-hidden-option :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> switch* {:default-checked enabled?
:aria-label label
:on-change handle-change}]]))
(mf/defc ruby-customization-options*
[{:keys [values on-change on-blur labels vertical]}]
(let [common-props (ruby-common-props values on-change on-blur labels vertical)
@ -444,7 +436,7 @@
{:ruby-size (ruby-size-options tr)
:ruby-align (ruby-align-options tr)
:ruby-overhang (ruby-overhang-options tr)})]
[:div {:class (stl/css :japanese-layout-controls)}
[:div {:class (stl/css :option-list)}
[:> ruby-hidden-option* common-props]
[:> span-select-option* (mf/spread-props common-props
{:attr :ruby-size
@ -466,6 +458,7 @@
:label (tr "workspace.options.text-options.ruby-side")})]]))
(mf/defc ruby-advanced-options*
"Reading input of a text selection, with its presentation options."
[{:keys [values on-change on-blur labels vertical]}]
(let [open* (mf/use-state false)
open? (deref open*)
@ -480,6 +473,8 @@
[:> ruby-customization-options* common-props]]]))
(mf/defc ruby-presentation-options*
"Presentation options of every reading of the texts, without a text
selection."
[{:keys [values on-change on-blur labels vertical]}]
(let [open* (mf/use-state false)
open? (deref open*)
@ -488,159 +483,10 @@
[:div {:class (stl/css :ruby-advanced-options)}
[:> title-bar* {:collapsable true
:collapsed (not open?)
:title (or (:advanced labels) (tr "workspace.options.text-options.ruby-advanced-options"))
:title (:advanced labels)
:on-collapsed toggle-open
:aria-expanded open?
:data-testid "ruby-presentation-options-toggle"}]
[:> advanced-options* {:class (stl/css :ruby-advanced-content)
:is-visible open?}
[:> ruby-customization-options* common-props]]]))
(mf/defc font-features-options*
;; Japanese proportional metric alternates, per span: `palt` for
;; horizontal text, `vpal` for vertical.
[{:keys [values on-change on-blur]}]
(let [writing-mode (:writing-mode values)
feature (proportional-metrics-feature writing-mode)
value (:font-features values)
enabled? (cond
(mixed-span-value? value) nil
(= feature value) true
:else false)
handle-change
(mf/use-fn
(mf/deps feature on-change on-blur)
(fn [checked?]
(on-change {:font-features (if checked? feature "none")})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)}
(tr "workspace.options.text-options.font-features")]
[:> switch* {:default-checked enabled?
:aria-label (tr "workspace.options.text-options.font-features")
:on-change handle-change}]]))
(defn text-emphasis-options
([]
(text-emphasis-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.text-emphasis-none")}
{:id "filled-dot"
:label (translate "workspace.options.text-options.text-emphasis-filled-dot")}
{:id "open-dot"
:label (translate "workspace.options.text-options.text-emphasis-open-dot")}
{:id "filled-circle"
:label (translate "workspace.options.text-options.text-emphasis-filled-circle")}
{:id "open-circle"
:label (translate "workspace.options.text-options.text-emphasis-open-circle")}
{:id "filled-sesame"
:label (translate "workspace.options.text-options.text-emphasis-filled-sesame")}
{:id "open-sesame"
:label (translate "workspace.options.text-options.text-emphasis-open-sesame")}]))
(defn annotation-clearance-options
([]
(annotation-clearance-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.annotation-clearance-none")}
{:id "auto"
:label (translate "workspace.options.text-options.annotation-clearance-auto")}]))
(defn line-adjustment-options
([]
(line-adjustment-options tr))
([translate]
[{:id "push-in-first"
:label (translate "workspace.options.text-options.line-adjustment-push-in-first")}
{:id "push-out-first"
:label (translate "workspace.options.text-options.line-adjustment-push-out-first")}
{:id "push-out-only"
:label (translate "workspace.options.text-options.line-adjustment-push-out-only")}]))
(mf/defc line-adjustment-option*
"Whole-shape setting: how lines slightly too long are fitted."
[{:keys [values on-change on-blur]}]
(let [options (mf/with-memo [] (line-adjustment-options))]
[:> span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :line-adjustment
:default-value (cjk/enum-default :line-adjustment)
:label (tr "workspace.options.text-options.line-adjustment")
:options options}]))
(mf/defc japanese-layout-options*
"Japanese options. `children` go below the writing mode row."
[{:keys [values ruby-values text-selection-active
on-change on-ruby-presentation-change on-blur children]}]
(let [vertical? (vertical-japanese-layout? values)
common-props (mf/props
{:values values
:on-change on-change
:on-blur on-blur
:vertical vertical?})
ruby-presentation-props (mf/props
{:values ruby-values
:on-change on-ruby-presentation-change
:on-blur on-blur
:vertical vertical?})
options (mf/with-memo []
{:writing-mode (writing-mode-options tr)
:text-orientation (text-orientation-options tr)
:warichu (warichu-options tr)
:text-emphasis (text-emphasis-options)
:annotation-clearance (annotation-clearance-options)})]
[:div {:class (stl/css :japanese-layout-options)}
[:div {:class (stl/css :japanese-layout-controls)}
[:div {:class (stl/css :japanese-icon-options)}
[:> attr-radio-options* (mf/spread-props common-props
{:attr :writing-mode
:default-value (cjk/enum-default :writing-mode)
:name "writing-mode-options"
:class (stl/css :writing-mode-options)
:options (:writing-mode options)})]
(when ^boolean vertical?
[:*
;; An unset orientation reads as nil, or "" in the v2 editor; both
;; select "mixed".
[:> attr-radio-options* (mf/spread-props common-props
{:attr :text-orientation
:default-value (cjk/enum-default :text-orientation)
:name "text-orientation-options"
:class (stl/css :text-orientation-options)
:options (:text-orientation options)})]
[:> text-combine-upright-options*
(mf/spread-props common-props
{:text-selection-active text-selection-active})]])
(when ^boolean text-selection-active
[:> attr-radio-options* (mf/spread-props common-props
{:attr :warichu
:default-value (cjk/enum-default :warichu)
:name "warichu-options"
:class (stl/css :warichu-options)
:options (:warichu options)})])]
children
(when ^boolean vertical?
[:> text-combine-upright-count-options* common-props])
[:> font-features-options* common-props]
[:> line-adjustment-option* common-props]
;; Emphasis marks (圏点 / bouten) apply to the selected characters.
(when ^boolean text-selection-active
[:> span-select-option* (mf/spread-props common-props
{:attr :text-emphasis
:default-value (cjk/enum-default :text-emphasis)
:label (tr "workspace.options.text-options.text-emphasis")
:options (:text-emphasis options)})])
[:> span-select-option* (mf/spread-props common-props
{:attr :annotation-clearance
:default-value (cjk/enum-default :annotation-clearance)
:label (tr "workspace.options.text-options.annotation-clearance")
:options (:annotation-clearance options)})]
(if text-selection-active
[:> ruby-advanced-options* common-props]
[:> ruby-presentation-options* ruby-presentation-props])]]))

View File

@ -9,13 +9,38 @@
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
.writing-mode-options,
.text-orientation-options,
.text-combine-upright-options,
.warichu-options {
.radio-options {
block-size: $sz-32;
}
.icon-options {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-xs);
}
.option-list {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.option-row {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
align-items: center;
gap: var(--sp-s);
inline-size: 100%;
min-block-size: $sz-32;
}
.option-label {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
}
.ruby-advanced-options {
display: flex;
flex-direction: column;
@ -40,39 +65,3 @@
border: $b-1 solid var(--color-background-quaternary);
border-radius: $br-8;
}
.japanese-layout-options,
.japanese-layout-controls {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.japanese-layout-options {
padding-block: var(--sp-xs);
}
.ruby-hidden-option {
min-block-size: $sz-32;
}
.japanese-icon-options {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-xs);
}
.japanese-select-option {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
align-items: center;
gap: var(--sp-s);
inline-size: 100%;
}
.japanese-option-label {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
}

View File

@ -5,6 +5,8 @@
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns app.main.ui.workspace.sidebar.options.menus.text-language-layout
"Language panel of the text options, and the options of each CJK
language below it. The controls they share are in `text-cjk-controls`."
(:require-macros [app.main.style :as stl])
(:require
[app.common.types.text.cjk-layout :as cjk]
@ -18,11 +20,253 @@
[app.main.ui.ds.controls.switch :refer [switch*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.main.ui.workspace.sidebar.options.menus.text-cjk-controls :as tcc]
[app.util.i18n :refer [tr]]
[app.util.text.layout :as tl]
[rumext.v2 :as mf]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Japanese
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn proportional-metrics-feature
"Proportional metrics feature for the writing mode: `vpal` or `palt`."
[writing-mode]
(if (= writing-mode "vertical-rl") "vpal" "palt"))
(defn text-combine-upright-options
[text-selection-active translate]
(cond-> [{:value "none"
:id "none-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-none")
:icon i/text-combine-upright-none}]
text-selection-active
(conj {:value "all"
:id "all-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-all")
:icon i/text-combine-upright-all})
true
(conj {:value "digits"
:id "digits-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-digits")
:icon i/text-combine-upright-digits})))
;; select* matches options by :id, so each id is the stored value.
(defn text-combine-upright-count-options
[translate]
[{:id "digits2"
:label (translate "workspace.options.text-options.text-combine-upright-digits-2")}
{:id "digits3"
:label (translate "workspace.options.text-options.text-combine-upright-digits-3")}
{:id "digits"
:label (translate "workspace.options.text-options.text-combine-upright-digits-4")}])
;; Warichu (割注): renders the selection as two half-size lines in one inline
;; position (top/bottom horizontally, right/left vertically).
(defn warichu-options
[translate]
[{:value "none"
:id "none-warichu"
:label (translate "workspace.options.text-options.warichu-none")
:icon i/warichu-none}
{:value "warichu"
:id "warichu-warichu"
:label (translate "workspace.options.text-options.warichu")
:icon i/warichu}])
(defn line-adjustment-options
([]
(line-adjustment-options tr))
([translate]
[{:id "push-in-first"
:label (translate "workspace.options.text-options.line-adjustment-push-in-first")}
{:id "push-out-first"
:label (translate "workspace.options.text-options.line-adjustment-push-out-first")}
{:id "push-out-only"
:label (translate "workspace.options.text-options.line-adjustment-push-out-only")}]))
(defn- text-combine-upright-value
[values]
(radio-selected (:text-combine-upright values) (cjk/enum-default :text-combine-upright)))
(mf/defc japanese-writing-mode-options*
"Radio groups Japanese adds to the writing mode row: tate-chu-yoko for
vertical text, warichu for a text selection. Digit tate-chu-yoko finds
eligible runs itself, so it applies to a whole shape; `all` is offered
only for a text selection."
[{:keys [values on-change on-blur text-selection-active]}]
(let [props (mf/props {:on-change on-change :on-blur on-blur})
value (:text-combine-upright values)
;; The digit counts are picked in their own select.
tcy (if (cjk/digit-combine? (radio-selected value)) "digits" value)
options (mf/with-memo [text-selection-active]
{:text-combine-upright (text-combine-upright-options text-selection-active tr)
:warichu (warichu-options tr)})]
[:*
(when (tcc/vertical-writing? values)
[:> tcc/attr-radio-options* (mf/spread-props props
{:values {:text-combine-upright tcy}
:attr :text-combine-upright
:default-value (cjk/enum-default :text-combine-upright)
:name "text-combine-upright-options"
:options (:text-combine-upright options)})])
(when ^boolean text-selection-active
[:> tcc/attr-radio-options* (mf/spread-props props
{:values values
:attr :warichu
:default-value (cjk/enum-default :warichu)
:name "warichu-options"
:options (:warichu options)})])]))
(mf/defc text-combine-upright-count-option*
[{:keys [values on-change on-blur]}]
(let [value (text-combine-upright-value values)
options (mf/with-memo [] (text-combine-upright-count-options tr))]
(when (cjk/digit-combine? value)
[:> tcc/span-select-option* {:values {:text-combine-upright value}
:on-change on-change
:on-blur on-blur
:attr :text-combine-upright
:default-value value
:label (tr "workspace.options.text-options.text-combine-upright-digits-count")
:options options}])))
(mf/defc font-features-option*
"Proportional metric alternates, per span: `palt` for horizontal text,
`vpal` for vertical."
[{:keys [values on-change on-blur]}]
(let [feature (proportional-metrics-feature (:writing-mode values))
value (:font-features values)
enabled? (cond
(tcc/mixed-span-value? value) nil
(= feature value) true
:else false)
label (tr "workspace.options.text-options.font-features")
handle-change
(mf/use-fn
(mf/deps feature on-change on-blur)
(fn [checked?]
(on-change {:font-features (if checked? feature "none")})
(when (some? on-blur) (on-blur))))]
[:> tcc/option-row* {:label label}
[:> switch* {:default-checked enabled?
:aria-label label
:on-change handle-change}]]))
(mf/defc line-adjustment-option*
"Whole-shape setting: how lines slightly too long are fitted."
[{:keys [values on-change on-blur]}]
(let [options (mf/with-memo [] (line-adjustment-options))]
[:> tcc/span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :line-adjustment
:default-value (cjk/enum-default :line-adjustment)
:label (tr "workspace.options.text-options.line-adjustment")
:options options}]))
(defn- japanese-reading-labels
[]
{:input (tr "workspace.options.text-options.ruby")
:placeholder (tr "workspace.options.text-options.ruby-placeholder")
:advanced (tr "workspace.options.text-options.ruby-advanced-options")})
(mf/defc japanese-layout-options*
"Japanese options below the writing mode row."
[{:keys [values ruby-values text-selection-active on-change on-ruby-presentation-change on-blur]}]
(let [vertical? (tcc/vertical-writing? values)
labels (japanese-reading-labels)
props (mf/props {:values values :on-change on-change :on-blur on-blur
:vertical vertical? :labels labels})]
[:*
(when ^boolean vertical?
[:> text-combine-upright-count-option* props])
[:> font-features-option* props]
[:> line-adjustment-option* props]
;; Emphasis marks (圏点 / bouten) apply to the selected characters.
(when ^boolean text-selection-active
[:> tcc/text-emphasis-option* props])
[:> tcc/annotation-clearance-option* props]
(if ^boolean text-selection-active
[:> tcc/ruby-advanced-options* props]
[:> tcc/ruby-presentation-options*
(mf/spread-props props {:values ruby-values
:on-change on-ruby-presentation-change})])]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Chinese
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc chinese-decoration-options*
[{:keys [values on-change on-blur]}]
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur
:vertical (tcc/vertical-writing? values)})]
[:*
[:> tcc/text-emphasis-option* props]
[:> tcc/position-select-option*
(mf/spread-props props {:attr :text-emphasis-position
:label (tr "workspace.options.text-options.emphasis-position")})]
[:> tcc/position-select-option*
(mf/spread-props props {:attr :text-underline-position
:label (tr "workspace.options.text-options.underline-position")})]
[:> tcc/annotation-clearance-option* props]]))
(defn- chinese-reading-labels
[zhuyin?]
(if zhuyin?
{:input (tr "workspace.options.text-options.zhuyin")
:placeholder (tr "workspace.options.text-options.zhuyin-placeholder")
:advanced (tr "workspace.options.text-options.pinyin-advanced-options")
:validate cjk/zhuyin-reading?
:invalid (tr "workspace.options.text-options.zhuyin-invalid")}
{:input (tr "workspace.options.text-options.pinyin")
:placeholder (tr "workspace.options.text-options.pinyin-placeholder")
:advanced (tr "workspace.options.text-options.pinyin-advanced-options")}))
(mf/defc chinese-reading-options*
"Pinyin or zhuyin readings. Zhuyin annotates one Han character at a time."
[{:keys [values ruby-values text-selection-active reading-selection-active zhuyin-selection-active
on-change on-ruby-presentation-change on-blur]}]
(let [zhuyin? (true? (:ruby-zhuyin values))
format-label (tr "workspace.options.text-options.chinese-reading-format")
handle-format-change (mf/use-fn
(mf/deps on-change)
(fn [value] (on-change {:ruby-zhuyin (= "zhuyin" value)})))
props (mf/props {:values values :on-change on-change :on-blur on-blur
:vertical (tcc/vertical-writing? values)
:labels (chinese-reading-labels zhuyin?)})
presentation-props (mf/spread-props props {:values ruby-values
:on-change on-ruby-presentation-change})]
[:*
(when reading-selection-active
[:> tcc/option-row* {:label format-label}
[:> select* {:default-selected (if zhuyin? "zhuyin" "pinyin")
:aria-label format-label
:options [{:id "pinyin" :label (tr "workspace.options.text-options.pinyin")}
{:id "zhuyin" :label (tr "workspace.options.text-options.zhuyin")
:disabled (not zhuyin-selection-active)}]
:on-change handle-format-change}]])
(if (and reading-selection-active (or (not zhuyin?) zhuyin-selection-active))
(if zhuyin?
[:* [:p {:class (stl/css :selection-hint)} (tr "workspace.options.text-options.zhuyin-selection-hint")]
[:> tcc/ruby-options* props]
[:> tcc/ruby-hidden-option* props]]
[:> tcc/ruby-advanced-options* props])
[:*
[:p {:class (stl/css :selection-hint)}
(if zhuyin? (tr "workspace.options.text-options.zhuyin-selection-hint") (tr "workspace.options.text-options.pinyin-selection-hint"))]
(when-not text-selection-active
(if zhuyin?
[:> tcc/ruby-hidden-option* presentation-props]
[:> tcc/ruby-presentation-options* presentation-props]))])]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Korean
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn korean-wrap-options
[value translate]
(cond-> [{:id "syllable" :label (translate "workspace.options.text-options.korean-wrap-syllable")}
@ -30,67 +274,19 @@
(= value "spaces-only")
(conj {:id "spaces-only" :label (translate "workspace.options.text-options.korean-wrap-spaces-only")})))
(mf/defc chinese-decoration-options*
(mf/defc korean-layout-options*
[{:keys [values on-change on-blur]}]
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur
:vertical (tjl/vertical-japanese-layout? values)})]
[:div {:class (stl/css :decoration-options)}
[:> tjl/span-select-option*
(mf/spread-props props {:attr :text-emphasis :default-value "none"
:label (tr "workspace.options.text-options.text-emphasis")
:options (tjl/text-emphasis-options tr)})]
[:> tjl/position-select-option*
(mf/spread-props props {:attr :text-emphasis-position
:label (tr "workspace.options.text-options.emphasis-position")})]
[:> tjl/position-select-option*
(mf/spread-props props {:attr :text-underline-position
:label (tr "workspace.options.text-options.underline-position")})]
[:> tjl/span-select-option*
(mf/spread-props props {:attr :annotation-clearance :default-value "none"
:label (tr "workspace.options.text-options.annotation-clearance")
:options (tjl/annotation-clearance-options)})]]))
[:> tcc/span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :korean-wrap
:default-value (cjk/enum-default :korean-wrap)
:label (tr "workspace.options.text-options.korean-wrap")
:options (korean-wrap-options (:korean-wrap values) tr)}])
(mf/defc chinese-reading-options*
[{:keys [values ruby-values text-selection-active reading-selection-active zhuyin-selection-active
on-change on-ruby-presentation-change on-blur]}]
(let [zhuyin? (true? (:ruby-zhuyin values))
labels {:input (if zhuyin? (tr "workspace.options.text-options.zhuyin") (tr "workspace.options.text-options.pinyin"))
:placeholder (if zhuyin? (tr "workspace.options.text-options.zhuyin-placeholder") (tr "workspace.options.text-options.pinyin-placeholder"))
:advanced (tr "workspace.options.text-options.pinyin-advanced-options")}
handle-format-change (mf/use-fn
(mf/deps on-change)
(fn [value] (on-change {:ruby-zhuyin (= "zhuyin" value)})))
props (mf/props {:values values :on-change on-change :on-blur on-blur
:vertical (tjl/vertical-japanese-layout? values)
:labels (cond-> labels zhuyin?
(assoc :validate cjk/zhuyin-reading?
:invalid (tr "workspace.options.text-options.zhuyin-invalid")))})]
[:div {:class (stl/css :pinyin-options)}
(when reading-selection-active
[:div {:class (stl/css :reading-format-option)}
[:span {:class (stl/css :option-label)} (tr "workspace.options.text-options.chinese-reading-format")]
[:> select* {:default-selected (if zhuyin? "zhuyin" "pinyin")
:aria-label (tr "workspace.options.text-options.chinese-reading-format")
:options [{:id "pinyin" :label (tr "workspace.options.text-options.pinyin")}
{:id "zhuyin" :label (tr "workspace.options.text-options.zhuyin")
:disabled (not zhuyin-selection-active)}]
:on-change handle-format-change}]])
(if (and reading-selection-active (or (not zhuyin?) zhuyin-selection-active))
(if zhuyin?
[:* [:p {:class (stl/css :selection-hint)} (tr "workspace.options.text-options.zhuyin-selection-hint")]
[:> tjl/ruby-options* props]
[:> tjl/ruby-hidden-option* props]]
[:> tjl/ruby-advanced-options* props])
[:*
[:p {:class (stl/css :selection-hint)}
(if zhuyin? (tr "workspace.options.text-options.zhuyin-selection-hint") (tr "workspace.options.text-options.pinyin-selection-hint"))]
(when-not text-selection-active
(if zhuyin?
[:> tjl/ruby-hidden-option*
(mf/spread-props props {:values ruby-values :on-change on-ruby-presentation-change})]
[:> tjl/ruby-presentation-options*
(mf/spread-props props {:values ruby-values
:on-change on-ruby-presentation-change})]))])]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Language panel
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc distribute-option*
"Distributed alignment (均等割り付け, 分散对齐, 배분 정렬): spreads the
@ -104,44 +300,31 @@
(fn [checked?]
(on-change {:text-align (if checked? "distribute" "left")})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :distribute-option)}
[:span {:class (stl/css :option-label)} label]
[:> tcc/option-row* {:label label}
[:> switch* {:default-checked (= "distribute" (radio-selected (:text-align values)))
:aria-label label
:on-change handle-change}]]))
(mf/defc language-layout-options*
"Options of the CJK `language` (`:japanese`, `:chinese` or `:korean`)."
[{:keys [language values ruby-values text-selection-active reading-selection-active zhuyin-selection-active
on-change on-ruby-presentation-change on-blur]}]
(let [props (mf/props {:values values :on-change on-change :on-blur on-blur})]
[:div {:class (stl/css :language-layout-options)}
;; Distribute goes below the writing mode row.
(if (= :japanese language)
[:> tjl/japanese-layout-options*
(mf/spread-props props {:ruby-values ruby-values
:text-selection-active text-selection-active
:on-ruby-presentation-change on-ruby-presentation-change})
[:> distribute-option* props]]
[:*
[:> tjl/writing-mode-controls* props]
[:> distribute-option* props]])
(when (= :chinese language)
[:> chinese-decoration-options* props])
(when (= :chinese language)
[:> chinese-reading-options*
(mf/spread-props props {:ruby-values ruby-values
:text-selection-active text-selection-active
:reading-selection-active reading-selection-active
:zhuyin-selection-active zhuyin-selection-active
:on-ruby-presentation-change on-ruby-presentation-change})])
(when (= :korean language)
[:> tjl/span-select-option*
(mf/spread-props props
{:attr :korean-wrap
:default-value "syllable"
:label (tr "workspace.options.text-options.korean-wrap")
:options (korean-wrap-options (:korean-wrap values) tr)})])]))
"Options of the CJK `language` (`:japanese`, `:chinese` or `:korean`):
the writing mode row and Distribute, which all share, then the options
of the language."
[{:keys [language values text-selection-active on-change on-blur] :as props}]
[:div {:class (stl/css :language-layout-options)}
[:> tcc/writing-mode-controls* {:values values :on-change on-change :on-blur on-blur}
(when (= :japanese language)
[:> japanese-writing-mode-options* {:values values
:on-change on-change
:on-blur on-blur
:text-selection-active text-selection-active}])]
[:> distribute-option* props]
(case language
:japanese [:> japanese-layout-options* props]
:chinese [:*
[:> chinese-decoration-options* props]
[:> chinese-reading-options* props]]
:korean [:> korean-layout-options* props]
nil)])
(mf/defc language-row*
"The language of the texts, opening the language dropdown, and the button

View File

@ -90,34 +90,6 @@
padding-block: var(--sp-xs);
}
.reading-format-option {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
align-items: center;
gap: var(--sp-s);
}
.distribute-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
min-block-size: $sz-32;
}
.option-label {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
}
.pinyin-options,
.decoration-options {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.selection-hint {
@include t.use-typography("body-small");

View File

@ -92,15 +92,15 @@
(recur (when continue? (rest styles)) taking? to result))
result))))
(def ^:private japanese-range-defaults
(def ^:private cjk-range-defaults
;; A range without ruby reports null to plugins.
(assoc cjk/span-attr-defaults :ruby nil))
(defn- shape-japanese-value
"Value of a Japanese span `attr` over a whole text shape proxy, with unset
(defn- shape-cjk-value
"Value of a CJK span `attr` over a whole text shape proxy, with unset
spans at their default: 'mixed' when spans differ."
[shape-proxy attr]
(let [default (get japanese-range-defaults attr)
(let [default (get cjk-range-defaults attr)
values (->> (-> shape-proxy u/proxy->shape :content)
(txt/node-seq txt/is-text-node?)
(map #(get % attr default)))]
@ -112,16 +112,16 @@
[shape-proxy attr]
(-> shape-proxy u/proxy->shape :content (cjk/whole-shape-attr attr) (d/nilv (cjk/enum-default attr))))
(defn- range-japanese-value
"Value of a Japanese span `attr` over a text range proxy's characters."
(defn- range-cjk-value
"Value of a CJK span `attr` over a text range proxy's characters."
[range-proxy start end attr]
(let [default (get japanese-range-defaults attr)]
(let [default (get cjk-range-defaults attr)]
(->> (-> range-proxy u/proxy->shape :content (content-range->text+styles start end))
(map #(get % attr default))
(u/mixed-value))))
(defn- enum-value?
"Validator accepting the values of the Japanese layout enum `attr`."
"Validator accepting the values of the CJK layout enum `attr`."
[attr]
(fn [value]
(cjk/valid-enum-value? attr value)))
@ -477,72 +477,72 @@
:fontFeatures
{:this true
:get (fn [self] (range-japanese-value self start end :font-features))
:get (fn [self] (range-cjk-value self start end :font-features))
:set (range-attr-setter plugin-id page-id id start end :fontFeatures :font-features (enum-value? :font-features))}
:textCombineUpright
{:this true
:get (fn [self] (range-japanese-value self start end :text-combine-upright))
:get (fn [self] (range-cjk-value self start end :text-combine-upright))
:set (range-attr-setter plugin-id page-id id start end :textCombineUpright :text-combine-upright (enum-value? :text-combine-upright))}
:textEmphasis
{:this true
:get (fn [self] (range-japanese-value self start end :text-emphasis))
:get (fn [self] (range-cjk-value self start end :text-emphasis))
:set (range-attr-setter plugin-id page-id id start end :textEmphasis :text-emphasis (enum-value? :text-emphasis))}
:textEmphasisPosition
{:this true
:get (fn [self] (range-japanese-value self start end :text-emphasis-position))
:get (fn [self] (range-cjk-value self start end :text-emphasis-position))
:set (range-attr-setter plugin-id page-id id start end :textEmphasisPosition :text-emphasis-position (enum-value? :text-emphasis-position))}
:textUnderlinePosition
{:this true
:get (fn [self] (range-japanese-value self start end :text-underline-position))
:get (fn [self] (range-cjk-value self start end :text-underline-position))
:set (range-attr-setter plugin-id page-id id start end :textUnderlinePosition :text-underline-position (enum-value? :text-underline-position))}
:warichu
{:this true
:get (fn [self] (range-japanese-value self start end :warichu))
:get (fn [self] (range-cjk-value self start end :warichu))
:set (range-annotation-setter plugin-id page-id id start end :warichu :warichu (enum-value? :warichu))}
:annotationClearance
{:this true
:get (fn [self] (range-japanese-value self start end :annotation-clearance))
:get (fn [self] (range-cjk-value self start end :annotation-clearance))
:set (range-attr-setter plugin-id page-id id start end :annotationClearance :annotation-clearance (enum-value? :annotation-clearance))}
:ruby
{:this true
:get (fn [self] (range-japanese-value self start end :ruby))
:get (fn [self] (range-cjk-value self start end :ruby))
:set (range-annotation-setter plugin-id page-id id start end :ruby :ruby optional-string?)}
:rubyHidden
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-hidden))
:get (fn [self] (range-cjk-value self start end :ruby-hidden))
:set (range-attr-setter plugin-id page-id id start end :rubyHidden :ruby-hidden boolean?)}
:rubyZhuyin
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-zhuyin))
:get (fn [self] (range-cjk-value self start end :ruby-zhuyin))
:set (range-attr-setter plugin-id page-id id start end :rubyZhuyin :ruby-zhuyin boolean?)}
:rubySize
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-size))
:get (fn [self] (range-cjk-value self start end :ruby-size))
:set (range-attr-setter plugin-id page-id id start end :rubySize :ruby-size (enum-value? :ruby-size))}
:rubyAlign
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-align))
:get (fn [self] (range-cjk-value self start end :ruby-align))
:set (range-attr-setter plugin-id page-id id start end :rubyAlign :ruby-align (enum-value? :ruby-align))}
:rubyOverhang
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-overhang))
:get (fn [self] (range-cjk-value self start end :ruby-overhang))
:set (range-attr-setter plugin-id page-id id start end :rubyOverhang :ruby-overhang (enum-value? :ruby-overhang))}
:rubySide
{:this true
:get (fn [self] (range-japanese-value self start end :ruby-side))
:get (fn [self] (range-cjk-value self start end :ruby-side))
:set (range-attr-setter plugin-id page-id id start end :rubySide :ruby-side (enum-value? :ruby-side))}
:lang
@ -978,59 +978,59 @@
:set (shape-attr-setter plugin-id page-id :textOrientation :text-orientation (enum-value? :text-orientation))}
{:name "textCombineUpright"
:get #(shape-japanese-value % :text-combine-upright)
:get #(shape-cjk-value % :text-combine-upright)
:set (shape-attr-setter plugin-id page-id :textCombineUpright :text-combine-upright (enum-value? :text-combine-upright))}
{:name "textEmphasis"
:get #(shape-japanese-value % :text-emphasis)
:get #(shape-cjk-value % :text-emphasis)
:set (shape-attr-setter plugin-id page-id :textEmphasis :text-emphasis (enum-value? :text-emphasis))}
{:name "textEmphasisPosition"
:get #(shape-japanese-value % :text-emphasis-position)
:get #(shape-cjk-value % :text-emphasis-position)
:set (shape-attr-setter plugin-id page-id :textEmphasisPosition :text-emphasis-position (enum-value? :text-emphasis-position))}
{:name "textUnderlinePosition"
:get #(shape-japanese-value % :text-underline-position)
:get #(shape-cjk-value % :text-underline-position)
:set (shape-attr-setter plugin-id page-id :textUnderlinePosition :text-underline-position (enum-value? :text-underline-position))}
{:name "warichu"
:get #(shape-japanese-value % :warichu)
:get #(shape-cjk-value % :warichu)
:set (shape-annotation-setter plugin-id page-id :warichu :warichu (enum-value? :warichu))}
{:name "fontFeatures"
:get #(shape-japanese-value % :font-features)
:get #(shape-cjk-value % :font-features)
:set (shape-attr-setter plugin-id page-id :fontFeatures :font-features (enum-value? :font-features))}
{:name "annotationClearance"
:get #(shape-japanese-value % :annotation-clearance)
:get #(shape-cjk-value % :annotation-clearance)
:set (shape-attr-setter plugin-id page-id :annotationClearance :annotation-clearance (enum-value? :annotation-clearance))}
{:name "ruby"
:get #(shape-japanese-value % :ruby)
:get #(shape-cjk-value % :ruby)
:set (shape-annotation-setter plugin-id page-id :ruby :ruby optional-string?)}
{:name "rubyHidden"
:get #(shape-japanese-value % :ruby-hidden)
:get #(shape-cjk-value % :ruby-hidden)
:set (shape-attr-setter plugin-id page-id :rubyHidden :ruby-hidden boolean?)}
{:name "rubyZhuyin"
:get #(shape-japanese-value % :ruby-zhuyin)
:get #(shape-cjk-value % :ruby-zhuyin)
:set (shape-attr-setter plugin-id page-id :rubyZhuyin :ruby-zhuyin boolean?)}
{:name "rubySize"
:get #(shape-japanese-value % :ruby-size)
:get #(shape-cjk-value % :ruby-size)
:set (shape-attr-setter plugin-id page-id :rubySize :ruby-size (enum-value? :ruby-size))}
{:name "rubyAlign"
:get #(shape-japanese-value % :ruby-align)
:get #(shape-cjk-value % :ruby-align)
:set (shape-attr-setter plugin-id page-id :rubyAlign :ruby-align (enum-value? :ruby-align))}
{:name "rubyOverhang"
:get #(shape-japanese-value % :ruby-overhang)
:get #(shape-cjk-value % :ruby-overhang)
:set (shape-attr-setter plugin-id page-id :rubyOverhang :ruby-overhang (enum-value? :ruby-overhang))}
{:name "rubySide"
:get #(shape-japanese-value % :ruby-side)
:get #(shape-cjk-value % :ruby-side)
:set (shape-attr-setter plugin-id page-id :rubySide :ruby-side (enum-value? :ruby-side))}
{:name "lang"

View File

@ -431,7 +431,7 @@
(when (= ev TEXT_EDITOR_EVENT_STYLES_CHANGED)
(let [current-styles (merge
(text-editor/text-editor-get-current-styles)
(text-editor/text-editor-get-current-japanese-styles))
(text-editor/text-editor-get-current-cjk-styles))
shape-id (text-editor/text-editor-get-active-shape-id)]
;; Keep the caret color matching the text at the caret position.
(text-editor/text-editor-apply-caret-color (:fills current-styles))

View File

@ -823,8 +823,8 @@
[para]
(apply + (map (fn [span] (count (:text span))) (:children para))))
(defn- span-japanese-styles
"Japanese span attributes with their defaults filled in. Every selection
(defn- span-cjk-styles
"CJK span attributes with their defaults filled in. Every selection
snapshot carries all of them, so moving onto an unstyled span resets the
controls."
[span]
@ -903,8 +903,8 @@
text (apply str (map :text (:children paragraph)))]
(cjk/zhuyin-base? (subs text start-offset end-offset)))))
(defn selection-japanese-styles
"Japanese span styles of a WASM selection, read from the cached content.
(defn selection-cjk-styles
"CJK span styles of a WASM selection, read from the cached content.
A range with differing values reports :multiple; a caret reports its
span's style."
[content selection]
@ -922,12 +922,12 @@
(selected-spans paragraphs sel-range))]
(when (seq spans)
(reduce (fn [result span]
(merge-selection-styles result (span-japanese-styles span)))
(merge-selection-styles result (span-cjk-styles span)))
{}
spans)))))
(defn selection-paragraph-styles
"Profile attrs of the selected paragraphs, including unset and mixed values."
"Language attrs of the selected paragraphs, including unset and mixed values."
[content selection]
(when (and content selection)
(let [{:keys [start-para end-para]} (normalize-selection selection)
@ -943,18 +943,18 @@
:multiple
value))))
result
[:text-language :korean-wrap]))
cjk/language-paragraph-attrs))
{}
(subvec paragraphs start-para (inc end-para))))))
(defn text-editor-get-current-japanese-styles
"Japanese span styles and paragraph profiles of the active editor selection."
(defn text-editor-get-current-cjk-styles
"CJK span styles and paragraph language attrs of the active editor selection."
[]
(when (wasm/ready?)
(let [shape-id (text-editor-get-active-shape-id)
selection (text-editor-get-selection)
content (get-cached-content shape-id)]
(merge (selection-japanese-styles content selection)
(merge (selection-cjk-styles content selection)
(selection-paragraph-styles content selection)))))
(defn- annotation-style-range

View File

@ -34,39 +34,50 @@
:multiple
(first languages)))))
(def ^:private language-groups
"Languages a text can take, grouped as the language dropdown lists them:
`[tag label-key]` pairs per group."
[{:id "japanese"
:label "workspace.options.text-options.language-group-japanese"
:languages [["ja" "workspace.options.text-options.language-japanese"]]}
{:id "korean"
:label "workspace.options.text-options.language-group-korean"
:languages [["ko" "workspace.options.text-options.language-korean"]]}
{:id "chinese"
:label "workspace.options.text-options.language-group-chinese"
;; Variant names, shown as "Chinese · <variant>" outside the group.
:variants true
:languages [["zh-Hans-CN" "workspace.options.text-options.language-chinese-mainland-simplified"]
["zh-Hant-CN" "workspace.options.text-options.language-chinese-mainland-traditional"]
["zh-Hant-TW" "workspace.options.text-options.language-chinese-taiwan"]
["zh-Hant-HK" "workspace.options.text-options.language-chinese-hong-kong"]]}])
(def ^:private language-entries
(into {}
(for [{:keys [variants languages]} language-groups
[tag label] languages]
[tag {:label label :variant variants}])))
(defn language-label
"Name of the language `tag` in the Language panel row: \"Mixed\" for
`:multiple`, the tag itself when it is not one of the listed languages."
[tag]
(case tag
:multiple (tr "settings.multiple")
"ja" (tr "workspace.options.text-options.profile-japanese")
"ko" (tr "workspace.options.text-options.profile-korean")
"zh-Hans-CN" (tr "workspace.options.text-options.language-chinese-variant"
(tr "workspace.options.text-options.profile-chinese-mainland-simplified"))
"zh-Hant-CN" (tr "workspace.options.text-options.language-chinese-variant"
(tr "workspace.options.text-options.profile-chinese-mainland-traditional"))
"zh-Hant-TW" (tr "workspace.options.text-options.language-chinese-variant"
(tr "workspace.options.text-options.profile-chinese-taiwan"))
"zh-Hant-HK" (tr "workspace.options.text-options.language-chinese-variant"
(tr "workspace.options.text-options.profile-chinese-hong-kong"))
tag))
(if (= :multiple tag)
(tr "settings.multiple")
(if-let [{:keys [label variant]} (get language-entries tag)]
(if variant
(tr "workspace.options.text-options.language-chinese-variant" (tr label))
(tr label))
tag)))
(defn language-options
"Rows of the language dropdown: one group per language, the `selected`
tag checked."
[selected]
(let [option (fn [tag label]
{:id tag :label label :selected (= selected tag)})]
[{:id "japanese" :header true
:label (tr "workspace.options.text-options.language-group-japanese")}
(option "ja" (tr "workspace.options.text-options.profile-japanese"))
{:id "korean" :header true
:label (tr "workspace.options.text-options.language-group-korean")}
(option "ko" (tr "workspace.options.text-options.profile-korean"))
{:id "chinese" :header true
:label (tr "workspace.options.text-options.language-group-chinese")}
(option "zh-Hans-CN" (tr "workspace.options.text-options.profile-chinese-mainland-simplified"))
(option "zh-Hant-CN" (tr "workspace.options.text-options.profile-chinese-mainland-traditional"))
(option "zh-Hant-TW" (tr "workspace.options.text-options.profile-chinese-taiwan"))
(option "zh-Hant-HK" (tr "workspace.options.text-options.profile-chinese-hong-kong"))]))
(into []
(mapcat (fn [{:keys [id label languages]}]
(into [{:id id :header true :label (tr label)}]
(map (fn [[tag label]]
{:id tag :label (tr label) :selected (= selected tag)}))
languages)))
language-groups))

View File

@ -62,7 +62,7 @@
does not match."
[content]
(and (not ^boolean (vertical-layout-active?))
(cjk/vertical-text-content? content)))
(cjk/stored-vertical? content)))
(defn renderable-paragraph
"Paragraph attrs as the active renderer lays them out: without the vertical
@ -70,7 +70,7 @@
[paragraph]
(if ^boolean (vertical-layout-active?)
paragraph
(apply dissoc paragraph cjk/whole-shape-paragraph-attrs)))
(apply dissoc paragraph cjk/vertical-writing-attrs)))
(defn renderable-content
"Text content as the active renderer lays it out (see `active-content` and

View File

@ -64,7 +64,7 @@
(t/is (false? (text-editor/reading-selection? content range))))
(t/is (false? (text-editor/reading-selection? nil nil)))))
(t/deftest japanese-styles-follow-the-selected-span
(t/deftest cjk-styles-follow-the-selected-span
(let [content (editor-content
[{:text "日" :ruby "にち" :text-emphasis "filled-dot"}
{:text "本"}])]
@ -82,7 +82,7 @@
:warichu "none"
:font-features "none"
:annotation-clearance "none"}
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 0 0 1))))
(t/is (= {:text-combine-upright "none"
:text-emphasis "none"
@ -98,7 +98,7 @@
:warichu "none"
:font-features "none"
:annotation-clearance "none"}
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 1 0 2))))))
(t/deftest paragraph-profiles-follow-the-editor-selection
@ -117,7 +117,7 @@
(t/is (= {:text-language :multiple :korean-wrap :multiple}
(text-editor/selection-paragraph-styles content (selection 1 1 0 0))))))
(t/deftest japanese-styles-report-mixed-ranges-and-follow-the-caret
(t/deftest cjk-styles-report-mixed-ranges-and-follow-the-caret
(let [content (editor-content
[{:text "平成"
:text-combine-upright "digits2"
@ -150,16 +150,16 @@
:warichu :multiple
:font-features :multiple
:annotation-clearance :multiple}
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 0 0 3))))
;; A caret at the boundary belongs to the preceding span, as in WASM.
(t/is (= "digits2"
(:text-combine-upright
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 2 0 2)))))
(t/is (= "warichu"
(:warichu
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 3 0 3)))))))
(t/deftest japanese-selection-offsets-are-utf16-code-units
@ -168,7 +168,7 @@
{:text "日" :ruby "にち"}])]
(t/is (= "にち"
(:ruby
(text-editor/selection-japanese-styles
(text-editor/selection-cjk-styles
content (selection 0 2 0 3)))))))
(t/deftest editor-history-restores-styled-content-and-selection
@ -545,7 +545,7 @@
(@#'v3-editor/follow-ime-caret! wrapper anchor)
(t/is (= "translate(24px, 72px)" (.. wrapper -style -transform))))))))
(t/deftest whole-shape-paragraph-attrs-apply-to-every-paragraph
(t/deftest vertical-writing-attrs-apply-to-every-paragraph
(let [content (-> (editor-content [{:text "一"}] [{:text "二"}] [{:text "三"}])
(assoc-in [:children 0 :children 2 :text-orientation] "upright"))
result (text-editor/apply-paragraph-attrs-to-range
@ -776,12 +776,12 @@
(t/is (= #{:japanese}
(cfnt/resolve-ambiguous-cjk (langs "東京都渋谷区神南一丁目") nil "ja"))))
(t/deftest wasm-enum-exports-match-the-japanese-value-table
(t/deftest wasm-enum-exports-match-the-cjk-value-table
(doseq [[attr values] cjk/enum-values]
(let [exported (js->clj (unchecked-get wasm/serializers (name attr)))]
(t/is (= values (mapv key (sort-by val exported))) (name attr)))))
(t/deftest unset-or-unknown-japanese-values-serialize-as-the-default
(t/deftest unset-or-unknown-cjk-values-serialize-as-the-default
(t/is (= 0 (sr/translate-text-layout-enum :ruby-side nil)))
(t/is (= 0 (sr/translate-text-layout-enum :ruby-side "")))
(t/is (= 0 (sr/translate-text-layout-enum :ruby-side "left")))

View File

@ -22,4 +22,4 @@
(t/deftest profile-text-language-shows-the-chosen-language
(t/is (str/includes? (render nil) "dashboard.text-language.none"))
(t/is (str/includes? (render "zh-Hant-TW") "workspace.options.text-options.language-chinese-variant"))
(t/is (str/includes? (render "ja") "workspace.options.text-options.profile-japanese")))
(t/is (str/includes? (render "ja") "workspace.options.text-options.language-japanese")))

View File

@ -16,7 +16,7 @@
[app.main.ui.shapes.text.styles :as text-styles]
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
[app.main.ui.workspace.sidebar.options.menus.text :as text-menu]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.main.ui.workspace.sidebar.options.menus.text-cjk-controls :as tcc]
[app.main.ui.workspace.sidebar.options.menus.text-language-layout :as tll]
[app.util.text.layout :as tl]
[app.util.text.writing-mode :as wm]
@ -95,7 +95,7 @@
(t/deftest language-row-names-mixed-and-unlisted-languages
(t/is (= "settings.multiple" (tl/language-label :multiple)))
(t/is (= "workspace.options.text-options.profile-japanese" (tl/language-label "ja")))
(t/is (= "workspace.options.text-options.language-japanese" (tl/language-label "ja")))
(t/is (= "zh-Hans" (tl/language-label "zh-Hans"))
"a tag outside the list shows as it is"))
@ -119,7 +119,7 @@
(t/deftest file-language-shows-its-options-without-a-remove-button
(let [markup (render-language-section "ja" nil)]
(t/is (str/includes? markup "workspace.options.text-options.profile-japanese"))
(t/is (str/includes? markup "workspace.options.text-options.language-japanese"))
(t/is (str/includes? markup "workspace.options.text-options.line-adjustment"))
(t/is (not (str/includes? markup "data-testid=\"text-language-remove\"")))
(t/is (not (str/includes? markup "data-testid=\"text-language-add\"")))))
@ -225,22 +225,22 @@
(t/is (= 3 (count (filter #(str/includes? % "checked=\"\"") inputs)))))))
(t/deftest text-emphasis-select-reports-and-restores-canonical-values
(let [options (tjl/text-emphasis-options identity)
(let [options (tcc/text-emphasis-options identity)
reported-id (:id (select/get-option options "filled-dot"))]
(t/is (= "filled-dot" reported-id))
(t/is (= "filled-dot"
(:id (select/get-option options reported-id))))))
(t/deftest annotation-clearance-select-reports-canonical-values
(let [options (tjl/annotation-clearance-options identity)]
(let [options (tcc/annotation-clearance-options identity)]
(t/is (= ["none" "auto"] (mapv :id options)))
(t/is (= "auto" (:id (select/get-option options "auto"))))))
(t/deftest unrestricted-tcy-is-only-offered-for-a-text-selection
(t/is (= ["none" "digits"]
(mapv :value (tjl/text-combine-upright-options false identity))))
(mapv :value (tll/text-combine-upright-options false identity))))
(t/is (= ["none" "all" "digits"]
(mapv :value (tjl/text-combine-upright-options true identity)))))
(mapv :value (tll/text-combine-upright-options true identity)))))
(t/deftest ruby-container-styles-preserve-customization
(let [style (text-styles/generate-ruby-container-styles
@ -253,8 +253,9 @@
(t/deftest advanced-furigana-options-are-collapsed-by-default
(let [markup (rds/renderToStaticMarkup
(mf/element tjl/ruby-presentation-options*
(mf/element tcc/ruby-presentation-options*
#js {:values {}
:labels {:advanced "workspace.options.text-options.ruby-advanced-options"}
:on-change identity
:on-blur identity}))]
(t/is (str/includes? markup "data-testid=\"ruby-presentation-options-toggle\""))
@ -263,7 +264,7 @@
(t/deftest advanced-furigana-options-include-hide-toggle
(let [markup (rds/renderToStaticMarkup
(mf/element tjl/ruby-hidden-option*
(mf/element tcc/ruby-hidden-option*
#js {:values {:ruby-hidden true}
:on-change identity
:on-blur identity}))]
@ -311,12 +312,12 @@
(t/is (false? (:ruby-hidden (second spans))))))
(t/deftest mixed-span-values-select-a-disabled-mixed-option
(let [options (tjl/with-mixed-span-option (tjl/text-emphasis-options identity) :multiple)]
(t/is (= "mixed" (tjl/span-select-value :multiple "none")))
(t/is (= "none" (tjl/span-select-value nil "none")))
(let [options (tcc/with-mixed-span-option (tcc/text-emphasis-options identity) :multiple)]
(t/is (= "mixed" (tcc/span-select-value :multiple "none")))
(t/is (= "none" (tcc/span-select-value nil "none")))
(t/is (= "mixed" (:id (last options))))
(t/is (true? (:disabled (last options))))
(t/is (= options (tjl/with-mixed-span-option options "none"))
(t/is (= options (tcc/with-mixed-span-option options "none"))
"an unmixed value adds no entry")))
(t/deftest mixed-radio-values-select-nothing
@ -384,35 +385,35 @@
(t/is (= "digits2" (:text-combine-upright text-values)))))
(t/deftest japanese-controls-distinguish-horizontal-and-vertical-modes
(t/is (false? (tjl/vertical-japanese-layout? {})))
(t/is (false? (tjl/vertical-japanese-layout?
(t/is (false? (tcc/vertical-writing? {})))
(t/is (false? (tcc/vertical-writing?
{:writing-mode "horizontal-tb"})))
(t/is (true? (tjl/vertical-japanese-layout?
(t/is (true? (tcc/vertical-writing?
{:writing-mode "vertical-rl"})))
(t/is (= "palt"
(tjl/proportional-metrics-feature "horizontal-tb")))
(tll/proportional-metrics-feature "horizontal-tb")))
(t/is (= "palt"
(tjl/proportional-metrics-feature nil)))
(tll/proportional-metrics-feature nil)))
(t/is (= "vpal"
(tjl/proportional-metrics-feature "vertical-rl"))))
(tll/proportional-metrics-feature "vertical-rl"))))
(t/deftest sidebar-options-offer-the-japanese-enum-values
(let [ids #(mapv :id %)
values #(mapv :value %)]
(t/is (= (:writing-mode cjk/enum-values) (values (tjl/writing-mode-options identity))))
(t/is (= (:text-orientation cjk/enum-values) (values (tjl/text-orientation-options identity))))
(t/is (= (:warichu cjk/enum-values) (values (tjl/warichu-options identity))))
(t/is (= (:text-emphasis cjk/enum-values) (ids (tjl/text-emphasis-options identity))))
(t/is (= (:annotation-clearance cjk/enum-values) (ids (tjl/annotation-clearance-options identity))))
(t/is (= (:ruby-size cjk/enum-values) (ids (tjl/ruby-size-options identity))))
(t/is (= (:ruby-align cjk/enum-values) (ids (tjl/ruby-align-options identity))))
(t/is (= (:ruby-overhang cjk/enum-values) (ids (tjl/ruby-overhang-options identity))))
(t/is (= (:ruby-side cjk/enum-values) (ids (tjl/position-options :ruby-side false))))
(t/is (= (:ruby-side cjk/enum-values) (ids (tjl/position-options :ruby-side true))))
(t/is (= (:line-adjustment cjk/enum-values) (ids (tjl/line-adjustment-options identity))))
(t/is (= (:writing-mode cjk/enum-values) (values (tcc/writing-mode-options identity))))
(t/is (= (:text-orientation cjk/enum-values) (values (tcc/text-orientation-options identity))))
(t/is (= (:warichu cjk/enum-values) (values (tll/warichu-options identity))))
(t/is (= (:text-emphasis cjk/enum-values) (ids (tcc/text-emphasis-options identity))))
(t/is (= (:annotation-clearance cjk/enum-values) (ids (tcc/annotation-clearance-options identity))))
(t/is (= (:ruby-size cjk/enum-values) (ids (tcc/ruby-size-options identity))))
(t/is (= (:ruby-align cjk/enum-values) (ids (tcc/ruby-align-options identity))))
(t/is (= (:ruby-overhang cjk/enum-values) (ids (tcc/ruby-overhang-options identity))))
(t/is (= (:ruby-side cjk/enum-values) (ids (tcc/position-options :ruby-side false))))
(t/is (= (:ruby-side cjk/enum-values) (ids (tcc/position-options :ruby-side true))))
(t/is (= (:line-adjustment cjk/enum-values) (ids (tll/line-adjustment-options identity))))
(t/is (every? #(cjk/valid-enum-value? :text-combine-upright %)
(values (tjl/text-combine-upright-options true identity))))
(t/is (every? cjk/digit-combine? (ids (tjl/text-combine-upright-count-options identity))))))
(values (tll/text-combine-upright-options true identity))))
(t/is (every? cjk/digit-combine? (ids (tll/text-combine-upright-count-options identity))))))
(t/deftest html-text-drops-vertical-writing-under-a-horizontal-renderer
(let [shape {:id (random-uuid) :width 100 :height 100
@ -557,7 +558,7 @@
(t/deftest japanese-layout-controls-include-line-adjustment
(let [markup (alignment-markup tjl/japanese-layout-options* "vertical-rl" true)]
(let [markup (alignment-markup tll/japanese-layout-options* "vertical-rl" true)]
(t/is (str/includes? markup "workspace.options.text-options.line-adjustment"))))
@ -603,35 +604,35 @@
(t/is (not (str/includes? html "<rt")))))
(t/deftest positions-show-only-the-sides-of-the-writing-mode
(let [labels #(mapv :label (tjl/position-options %1 %2))]
(let [labels #(mapv :label (tcc/position-options %1 %2))]
(t/is (= ["workspace.options.text-options.position-over"
"workspace.options.text-options.position-under"]
(labels :ruby-side false)))
(t/is (= ["workspace.options.text-options.position-right"
"workspace.options.text-options.position-left"]
(labels :ruby-side true)))
(t/is (= ["auto" "over" "under"] (mapv :id (tjl/position-options :text-emphasis-position false))))
(t/is (= ["auto" "right" "left"] (mapv :id (tjl/position-options :text-emphasis-position true))))
(t/is (= ["auto" "under"] (mapv :id (tjl/position-options :text-underline-position false))))
(t/is (= ["auto" "right" "left"] (mapv :id (tjl/position-options :text-underline-position true))))))
(t/is (= ["auto" "over" "under"] (mapv :id (tcc/position-options :text-emphasis-position false))))
(t/is (= ["auto" "right" "left"] (mapv :id (tcc/position-options :text-emphasis-position true))))
(t/is (= ["auto" "under"] (mapv :id (tcc/position-options :text-underline-position false))))
(t/is (= ["auto" "right" "left"] (mapv :id (tcc/position-options :text-underline-position true))))))
(t/deftest picking-a-position-keeps-the-side-of-the-other-writing-mode
(t/is (= "under" (tjl/position-choice :text-emphasis-position false "under-left")))
(t/is (= "left" (tjl/position-choice :text-emphasis-position true "under-left")))
(t/is (= "auto" (tjl/position-choice :text-emphasis-position true "auto")))
(t/is (= "under-left" (tjl/position-value :text-emphasis-position false "over-left" "under")))
(t/is (= "under-right" (tjl/position-value :text-emphasis-position true "under-left" "right")))
(t/is (= "over-right" (tjl/position-value :text-emphasis-position false "auto" "over")))
(t/is (= "auto" (tjl/position-value :text-emphasis-position false "under-left" "auto")))
(t/is (= "under" (tjl/position-choice :text-underline-position false "under-right")))
(t/is (= "auto" (tjl/position-choice :text-underline-position false "left")))
(t/is (= "right" (tjl/position-choice :text-underline-position true "under-right")))
(t/is (= "under-left" (tjl/position-value :text-underline-position false "left" "under")))
(t/is (= "left" (tjl/position-value :text-underline-position false "under-left" "auto")))
(t/is (= "under-right" (tjl/position-value :text-underline-position true "under" "right")))
(t/is (= "under" (tjl/position-value :text-underline-position true "under-left" "auto")))
(t/is (= "under" (tjl/position-value :ruby-side true "over" "under")))
(t/is (= :multiple (tjl/position-choice :text-emphasis-position false :multiple))))
(t/is (= "under" (tcc/position-choice :text-emphasis-position false "under-left")))
(t/is (= "left" (tcc/position-choice :text-emphasis-position true "under-left")))
(t/is (= "auto" (tcc/position-choice :text-emphasis-position true "auto")))
(t/is (= "under-left" (tcc/position-value :text-emphasis-position false "over-left" "under")))
(t/is (= "under-right" (tcc/position-value :text-emphasis-position true "under-left" "right")))
(t/is (= "over-right" (tcc/position-value :text-emphasis-position false "auto" "over")))
(t/is (= "auto" (tcc/position-value :text-emphasis-position false "under-left" "auto")))
(t/is (= "under" (tcc/position-choice :text-underline-position false "under-right")))
(t/is (= "auto" (tcc/position-choice :text-underline-position false "left")))
(t/is (= "right" (tcc/position-choice :text-underline-position true "under-right")))
(t/is (= "under-left" (tcc/position-value :text-underline-position false "left" "under")))
(t/is (= "left" (tcc/position-value :text-underline-position false "under-left" "auto")))
(t/is (= "under-right" (tcc/position-value :text-underline-position true "under" "right")))
(t/is (= "under" (tcc/position-value :text-underline-position true "under-left" "auto")))
(t/is (= "under" (tcc/position-value :ruby-side true "over" "under")))
(t/is (= :multiple (tcc/position-choice :text-emphasis-position false :multiple))))
(t/deftest distribute-comes-after-the-writing-mode-row
(with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))]

View File

@ -10050,11 +10050,11 @@ msgid "mcp.oauth.cancelled"
msgstr "Connection cancelled"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-japanese"
msgid "workspace.options.text-options.language-japanese"
msgstr "Japanese"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-korean"
msgid "workspace.options.text-options.language-korean"
msgstr "Korean"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
@ -10070,19 +10070,19 @@ msgid "workspace.options.text-options.korean-wrap-spaces-only"
msgstr "At spaces (experimental)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-mainland-simplified"
msgid "workspace.options.text-options.language-chinese-mainland-simplified"
msgstr "Mainland China (Simplified)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-mainland-traditional"
msgid "workspace.options.text-options.language-chinese-mainland-traditional"
msgstr "Mainland China (Traditional)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-taiwan"
msgid "workspace.options.text-options.language-chinese-taiwan"
msgstr "Taiwan"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-hong-kong"
msgid "workspace.options.text-options.language-chinese-hong-kong"
msgstr "Hong Kong"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs

View File

@ -9966,11 +9966,11 @@ msgid "mcp.oauth.cancelled"
msgstr "Conexión cancelada"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-japanese"
msgid "workspace.options.text-options.language-japanese"
msgstr "Japonés"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-korean"
msgid "workspace.options.text-options.language-korean"
msgstr "Coreano"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
@ -9986,19 +9986,19 @@ msgid "workspace.options.text-options.korean-wrap-spaces-only"
msgstr "En los espacios (experimental)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-mainland-simplified"
msgid "workspace.options.text-options.language-chinese-mainland-simplified"
msgstr "China continental (simplificado)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-mainland-traditional"
msgid "workspace.options.text-options.language-chinese-mainland-traditional"
msgstr "China continental (tradicional)"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-taiwan"
msgid "workspace.options.text-options.language-chinese-taiwan"
msgstr "Taiwán"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs
msgid "workspace.options.text-options.profile-chinese-hong-kong"
msgid "workspace.options.text-options.language-chinese-hong-kong"
msgstr "Hong Kong"
#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs