diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 0ab7d9ae75..53f7c48cbe 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -63,6 +63,8 @@ [:plugins {:optional true} ctp/schema:plugin-registry] [:renderer {:optional true} [::sm/one-of #{:svg :wasm}]] [:japanese-layout-all-files {:optional true} ::sm/boolean] + [:chinese-layout-all-files {:optional true} ::sm/boolean] + [:korean-layout-all-files {:optional true} ::sm/boolean] [:mcp-enabled {:optional true} ::sm/boolean] [:newsletter-updates {:optional true} ::sm/boolean] [:newsletter-news {:optional true} ::sm/boolean] diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index 7475e389e2..836e504277 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -179,6 +179,29 @@ (t/is (nil? (:error out))) (t/is (true? (get-in out [:result :props :japanese-layout-all-files])))))) +(t/deftest update-profile-language-layout-settings + (let [profile (th/create-profile* 1) + props {:japanese-layout-all-files true + :chinese-layout-all-files true + :korean-layout-all-files false} + params {::th/type :update-profile-props + ::rpc/profile-id (:id profile) + :props props} + out (th/command! params)] + (t/is (nil? (:error out))) + (t/is (= props (select-keys (:result out) (keys props)))) + (let [out (th/command! {::th/type :get-profile + ::rpc/profile-id (:id profile)})] + (t/is (= props (select-keys (get-in out [:result :props]) (keys props))))) + (let [out (th/command! (assoc params :props {:chinese-layout-all-files false}))] + (t/is (nil? (:error out)))) + (let [out (th/command! {::th/type :get-profile + ::rpc/profile-id (:id profile)})] + (t/is (= {:japanese-layout-all-files true + :chinese-layout-all-files false + :korean-layout-all-files false} + (select-keys (get-in out [:result :props]) (keys props))))))) + (t/deftest profile-deletion-1 (let [prof (th/create-profile* 1) file (th/create-file* 1 {:profile-id (:id prof) diff --git a/common/src/app/common/attrs.cljc b/common/src/app/common/attrs.cljc index 205b02ebd4..60e6e39eef 100644 --- a/common/src/app/common/attrs.cljc +++ b/common/src/app/common/attrs.cljc @@ -42,6 +42,11 @@ :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) + (get obj attr) + :else (get obj attr ::unset)))) diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc index 281ed4c9bb..288c28a593 100644 --- a/common/src/app/common/features.cljc +++ b/common/src/app/common/features.cljc @@ -57,6 +57,7 @@ "text-editor/v2" "text-editor-wasm/v1" "text-editor-wasm/v1-html-paste" + "vertical-text-layout" "render-wasm/v1" "variants/v1"}) @@ -84,6 +85,7 @@ "text-editor-wasm/v1" "text-editor-wasm/v1-html-paste" "tokens/numeric-input" + "vertical-text-layout" "render-wasm/v1"}) ;; Features that are mainly backend only or there are a proper @@ -134,6 +136,7 @@ :feature-text-editor-v2-html-paste "text-editor/v2-html-paste" :feature-text-editor-wasm "text-editor-wasm/v1" :feature-text-editor-wasm-html-paste "text-editor-wasm/v1-html-paste" + :vertical-text-layout "vertical-text-layout" :feature-render-wasm "render-wasm/v1" :feature-variants "variants/v1" :feature-token-input "tokens/numeric-input" diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index baffa8d91d..3250be7d35 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -440,9 +440,10 @@ [:file-id ::sm/uuid] [:library-id [:maybe ::sm/uuid]]]] - [:set-japanese-layout - [:map {:title "SetJapaneseLayout"} - [:type [:= :set-japanese-layout]] + [:set-text-layout + [:map {:title "SetTextLayout"} + [:type [:= :set-text-layout]] + [:language [:enum :japanese :chinese :korean]] [:enabled ::sm/boolean]]]]) (def schema:changes @@ -1097,9 +1098,9 @@ [data {:keys [library-id]}] (cfo/set-tokens-source data library-id)) -(defmethod process-change :set-japanese-layout - [data {:keys [enabled]}] - (ctf/set-japanese-layout-enabled data enabled)) +(defmethod process-change :set-text-layout + [data {:keys [language enabled]}] + (ctf/set-text-layout-enabled data language enabled)) ;; === Operations diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index d948196886..783b7416d1 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -1150,15 +1150,17 @@ :library-id prev-val}) (apply-changes-local)))) -(defn set-japanese-layout - [changes enabled?] +(defn set-text-layout + [changes language enabled?] (assert-file-data! changes) - (let [file-data (::file-data (meta changes)) - previous-enabled (ctf/japanese-layout-enabled? file-data)] + (let [file-data (::file-data (meta changes)) + previous-enabled (ctf/text-layout-enabled? file-data language)] (-> changes - (update :redo-changes conj {:type :set-japanese-layout + (update :redo-changes conj {:type :set-text-layout + :language language :enabled enabled?}) - (update :undo-changes conj {:type :set-japanese-layout + (update :undo-changes conj {:type :set-text-layout + :language language :enabled previous-enabled}) (apply-changes-local)))) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 378f1e6d08..9533b9d94e 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -156,6 +156,7 @@ ;; Show WASM renderer info label (hidden by default). :render-wasm-info :render-switch + :vertical-text-layout :hide-release-modal :subscriptions :inspect-styles diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 6d4914ee06..d0ca2cd39a 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -78,7 +78,9 @@ [:map {:title "FileOptions"} [:components-v2 {:optional true} ::sm/boolean] [:base-font-size {:optional true} :string] - [:japanese-layout {:optional true} ::sm/boolean]]) + [:japanese-layout {:optional true} ::sm/boolean] + [:chinese-layout {:optional true} ::sm/boolean] + [:korean-layout {:optional true} ::sm/boolean]]) (def schema:data [:map {:title "FileData"} @@ -1256,17 +1258,32 @@ (assoc-in file-data [:options :base-font-size] base-font-size)) -;; Japanese text layout +;; Text layout controls -(defn japanese-layout-enabled? - [file-data] - (true? (get-in file-data [:options :japanese-layout]))) +(defn text-layout-option + [language] + (case language + :japanese :japanese-layout + :chinese :chinese-layout + :korean :korean-layout)) -(defn set-japanese-layout-enabled - [file-data enabled?] - (if enabled? - (assoc-in file-data [:options :japanese-layout] true) - (d/update-in-when file-data [:options] dissoc :japanese-layout))) +(defn text-layout-preference + [language] + (case language + :japanese :japanese-layout-all-files + :chinese :chinese-layout-all-files + :korean :korean-layout-all-files)) + +(defn text-layout-enabled? + [file-data language] + (true? (get-in file-data [:options (text-layout-option language)]))) + +(defn set-text-layout-enabled + [file-data language enabled?] + (let [option (text-layout-option language)] + (if enabled? + (assoc-in file-data [:options option] true) + (d/update-in-when file-data [:options] dissoc option)))) ;; Ref Chains diff --git a/common/test/common_tests/attrs_test.cljc b/common/test/common_tests/attrs_test.cljc index 340a89e0da..9899e6efb8 100644 --- a/common/test/common_tests/attrs_test.cljc +++ b/common/test/common_tests/attrs_test.cljc @@ -148,4 +148,12 @@ {:x -153}] result (attrs/get-attrs-multi objs [:x])] (t/is (= {:x :multiple} result) - "Different positions should be :multiple")))) \ No newline at end of file + "Different positions should be :multiple")))) +(t/deftest unset-language-profile-counts-as-a-distinct-value + (t/is (= {:text-language :multiple :korean-wrap :multiple} + (attrs/get-attrs-multi [{} {:text-language "ko" :korean-wrap "spaces-only"}] + [:text-language :korean-wrap]))) + (t/is (= {:text-language :multiple} + (attrs/get-attrs-multi [{:text-language "ko"} {}] [:text-language]))) + (t/is (= {:text-language nil :korean-wrap nil} + (attrs/get-attrs-multi [{} {}] [:text-language :korean-wrap])))) diff --git a/common/test/common_tests/files_changes_test.cljc b/common/test/common_tests/files_changes_test.cljc index 0d6dbc86f6..7936eea061 100644 --- a/common/test/common_tests/files_changes_test.cljc +++ b/common/test/common_tests/files_changes_test.cljc @@ -251,25 +251,39 @@ "The parent must use the updated bounds of both children") (assert-idempotent data changes)))))) -(t/deftest set-japanese-layout-file-option - (let [file-id (uuid/custom 2 2) - page-id (uuid/custom 1 1) - data (make-file-data file-id page-id) - enabled (ch/process-changes data [{:type :set-japanese-layout - :enabled true}]) - disabled (ch/process-changes enabled [{:type :set-japanese-layout - :enabled false}]) - changes (-> (pcb/empty-changes) - (pcb/with-file-data data) - (pcb/set-japanese-layout true))] - (t/is (false? (ctf/japanese-layout-enabled? data))) - (t/is (true? (ctf/japanese-layout-enabled? enabled))) - (t/is (false? (ctf/japanese-layout-enabled? disabled))) - (t/is (not (contains? (:options disabled) :japanese-layout))) - (t/is (= [{:type :set-japanese-layout :enabled true}] - (:redo-changes changes))) - (t/is (= '({:type :set-japanese-layout :enabled false}) - (:undo-changes changes))))) +(t/deftest language-layout-changes-are-independent + (let [data (assoc (make-file-data (uuid/custom 2 2) (uuid/custom 1 1)) + :options {:japanese-layout true :base-font-size "16"})] + (doseq [language [:japanese :chinese :korean]] + (let [change {:type :set-text-layout :language language :enabled true} + enabled (ch/process-changes data [change]) + disabled (ch/process-changes enabled [(assoc change :enabled false)]) + option (case language + :japanese :japanese-layout + :chinese :chinese-layout + :korean :korean-layout)] + (t/is (ch/valid-change? change)) + (t/is (true? (get-in enabled [:options option]))) + (t/is (not (contains? (:options disabled) option))) + (t/is (= (dissoc (:options data) option) (:options disabled))) + (t/is (= (dissoc data :options) (dissoc enabled :options)))))) + (t/is (not (ch/valid-change? {:type :set-text-layout :language :english + :enabled true})))) + +(t/deftest language-layout-switches-round-trip-through-undo + (let [data (assoc (make-file-data (uuid/custom 2 2) (uuid/custom 1 1)) + :options {:japanese-layout true :base-font-size "16"})] + (doseq [language [:japanese :chinese :korean] + enabled? [true false]] + (let [changes (-> (pcb/empty-changes) + (pcb/with-file-data data) + (pcb/set-text-layout language enabled?)) + edited (ch/process-changes data (:redo-changes changes)) + restored (ch/process-changes edited (:undo-changes changes))] + (t/is (= enabled? (ctf/text-layout-enabled? edited language))) + (t/is (= data restored)) + (t/is (sm/validate ctf/schema:options (json-roundtrip (:options edited)))) + (t/is (= (:options edited) (json-roundtrip (:options edited)))))))) (t/deftest add-obj (let [file-id (uuid/custom 2 2) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ccd7c1ecae..141fb2c025 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -679,15 +679,15 @@ (dm/export layout/toggle-layout-flag) (dm/export layout/remove-layout-flag) -(defn set-japanese-layout - [enabled?] - (ptk/reify ::set-japanese-layout +(defn set-text-layout + [language enabled?] + (ptk/reify ::set-text-layout ptk/WatchEvent (watch [it state _] (let [file-data (dsh/lookup-file-data state) - changes (-> (pcb/empty-changes it) - (pcb/with-file-data file-data) - (pcb/set-japanese-layout enabled?))] + changes (-> (pcb/empty-changes it) + (pcb/with-file-data file-data) + (pcb/set-text-layout language enabled?))] (rx/of (dch/commit-changes changes)))))) ;; --- Profile diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index ddf143cae0..c449e7554d 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -290,13 +290,21 @@ "Currently working file data on workspace" (l/derived dsh/lookup-file-data st/state)) -(def japanese-layout-config - "Japanese layout switches: `:file?` for the workspace file and - `:all-files?` from the profile." - (l/derived (fn [state] - {:file? (ctf/japanese-layout-enabled? (dsh/lookup-file-data state)) - :all-files? (true? (dm/get-in state [:profile :props :japanese-layout-all-files]))}) - st/state =)) +(def text-layout-config + "Control availability by language; it never changes paragraph profiles." + (l/derived + (fn [state] + (let [file-data (dsh/lookup-file-data state) + props (dm/get-in state [:profile :props]) + feature? (contains? (:features state) "vertical-text-layout") + config (fn [language] + {:feature-enabled feature? + :file? (ctf/text-layout-enabled? file-data language) + :all-files? (true? (get props (ctf/text-layout-preference language)))})] + {:japanese (config :japanese) + :chinese (config :chinese) + :korean (config :korean)})) + st/state =)) (def workspace-file-colors (l/derived (fn [{:keys [id] :as data}] diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index dee7ba1d0a..d0c36ada1c 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -7,10 +7,12 @@ (ns app.main.ui.settings.options (:require-macros [app.main.style :as stl]) (:require + [app.common.types.file :as ctf] [app.config :as cf] [app.main.data.event :as ev] [app.main.data.notifications :as ntf] [app.main.data.profile :as du] + [app.main.features :as features] [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] @@ -115,35 +117,46 @@ :on-change handle-render-change}]] [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]])) -(mf/defc japanese-layout-settings* - [{:keys [enabled]}] +(mf/defc language-layout-setting* + [{:keys [language label enabled]}] (let [handle-change (mf/use-fn + (mf/deps language) (fn [enabled?] - (st/emit! (ev/event {::ev/name (if enabled? - "enable-japanese-layout-all-files" - "disable-japanese-layout-all-files") + (st/emit! (ev/event {::ev/name (str (if enabled? "enable-" "disable-") + (name language) "-layout-all-files") ::ev/origin "settings"}) (du/update-profile-props - {:japanese-layout-all-files enabled?}) + {(ctf/text-layout-preference language) enabled?}) (ntf/success (tr "notifications.profile-saved")))))] - [:section {:class (stl/css :japanese-layout-container)} - [:> heading* {:class (stl/css :title) - :level 2 - :typography t/title-large} - (tr "dashboard.japanese-layout.title")] - [:> text* {:class (stl/css :description) :typography t/body-medium} - (tr "dashboard.japanese-layout.description")] - [:form {:class (stl/css :japanese-layout-form)} - [:> switch* {:label (tr "dashboard.japanese-layout.enable-all-files") - :default-checked enabled - :on-change handle-change}]]])) + [:> switch* {:label label + :default-checked enabled + :on-change handle-change}])) + +(mf/defc language-layout-settings* + [{:keys [config]}] + [:section {:class (stl/css :text-layout-container)} + [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} + (tr "dashboard.text-layout.title")] + [:> text* {:class (stl/css :description) :typography t/body-medium} + (tr "dashboard.text-layout.description")] + [:div {:class (stl/css :text-layout-form)} + [:> language-layout-setting* {:language :japanese + :label (tr "dashboard.japanese-layout.enable-all-files") + :enabled (get-in config [:japanese :all-files?])}] + [:> language-layout-setting* {:language :chinese + :label (tr "dashboard.chinese-layout.enable-all-files") + :enabled (get-in config [:chinese :all-files?])}] + [:> language-layout-setting* {:language :korean + :label (tr "dashboard.korean-layout.enable-all-files") + :enabled (get-in config [:korean :all-files?])}]]]) (mf/defc options-page* [] (let [profile (mf/deref refs/profile) renderer (or (-> profile :props :renderer) :svg) - japanese-layout-enabled (:all-files? (mf/deref refs/japanese-layout-config))] + layout-config (mf/deref refs/text-layout-config) + layout-feature? (features/use-feature "vertical-text-layout")] (mf/use-effect #(dom/set-html-title (tr "title.settings.options"))) @@ -157,6 +170,7 @@ :id "options-section-title"} (tr "labels.settings")] [:> options-form*]] - [:> japanese-layout-settings* {:enabled japanese-layout-enabled}] + (when layout-feature? + [:> language-layout-settings* {:config layout-config}]) (when (contains? cf/flags :render-switch) [:> webgl-settings* {:renderer renderer}])]])) diff --git a/frontend/src/app/main/ui/settings/options.scss b/frontend/src/app/main/ui/settings/options.scss index af13b89b95..5256ac1938 100644 --- a/frontend/src/app/main/ui/settings/options.scss +++ b/frontend/src/app/main/ui/settings/options.scss @@ -23,7 +23,7 @@ /* Settings sections with standalone controls. */ .webgl-container, -.japanese-layout-container { +.text-layout-container { display: grid; grid-auto-rows: auto; gap: var(--sp-s); @@ -34,7 +34,10 @@ color: var(--color-foreground-primary); } -.japanese-layout-form { +.text-layout-form { + display: flex; + flex-direction: column; + gap: var(--sp-s); padding-block-start: var(--sp-xs); } diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 93c5ffd753..cc5033f6c7 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -45,7 +45,7 @@ [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] - [app.util.text.writing-mode :as wm] + [app.util.text.layout :as tl] [beicon.v2.core :as rx] [potok.v2.core :as ptk] [rumext.v2 :as mf])) @@ -231,13 +231,28 @@ [:span {:class (stl/css :feedback :item-name)} (tr "labels.give-feedback")]])])) +(mf/defc language-layout-menu-item* + [{:keys [language config can-edit read-only label on-toggle]}] + (let [toggle (mf/use-fn + (mf/deps language on-toggle) + #(on-toggle language %))] + (when (tl/file-action-visible? config can-edit read-only) + [:> dropdown-menu-item* {:on-click toggle + :class (stl/css :base-menu-item :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) (toggle event))) + :data-testid (str (name language) "-layout") + :id (str "file-menu-" (name language) "-layout")} + [:span {:class (stl/css :item-name)} label]]))) + (mf/defc preferences-menu* {::mf/private true ::mf/wrap [mf/memo]} - [{:keys [japanese-layout-enabled japanese-layout-all-files layout profile toggle-flag - on-close toggle-theme toggle-render toggle-japanese-layout]}] + [{:keys [layout-config layout profile toggle-flag + on-close toggle-theme toggle-render toggle-text-layout]}] (let [renderer (or (-> profile :props :renderer) :svg) read-only? (mf/use-ctx ctx/workspace-read-only?) + can-edit (:can-edit (mf/deref refs/permissions)) show-nudge-options (mf/use-fn @@ -320,20 +335,24 @@ :id "file-menu-nudge"} [:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]] - (when (and (not read-only?) - (not japanese-layout-all-files) - (wm/vertical-layout-active?)) - [:> dropdown-menu-item* {:on-click toggle-japanese-layout - :class (stl/css :base-menu-item :submenu-item) - :on-key-down (fn [event] - (when (kbd/enter? event) - (toggle-japanese-layout event))) - :data-testid "japanese-layout" - :id "file-menu-japanese-layout"} - [:span {:class (stl/css :item-name)} - (if japanese-layout-enabled - (tr "workspace.header.menu.disable-japanese-layout") - (tr "workspace.header.menu.enable-japanese-layout"))]]) + [:> language-layout-menu-item* + {:language :japanese :config (:japanese layout-config) + :can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout + :label (if (get-in layout-config [:japanese :file?]) + (tr "workspace.header.menu.disable-japanese-layout") + (tr "workspace.header.menu.enable-japanese-layout"))}] + [:> language-layout-menu-item* + {:language :chinese :config (:chinese layout-config) + :can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout + :label (if (get-in layout-config [:chinese :file?]) + (tr "workspace.header.menu.disable-chinese-layout") + (tr "workspace.header.menu.enable-chinese-layout"))}] + [:> language-layout-menu-item* + {:language :korean :config (:korean layout-config) + :can-edit can-edit :read-only read-only? :on-toggle toggle-text-layout + :label (if (get-in layout-config [:korean :file?]) + (tr "workspace.header.menu.disable-korean-layout") + (tr "workspace.header.menu.enable-korean-layout"))}] [:> dropdown-menu-item* {:on-click toggle-theme :class (stl/css :base-menu-item :submenu-item) @@ -904,8 +923,7 @@ (mf/defc menu* [{:keys [layout file]}] (let [profile (mf/deref refs/profile) - japanese-layout (mf/deref refs/japanese-layout-config) - japanese-layout-enabled (:file? japanese-layout) + layout-config (mf/deref refs/text-layout-config) show-menu* (mf/use-state false) show-menu? (deref show-menu*) @@ -967,12 +985,12 @@ (dom/stop-propagation event) (st/emit! (du/toggle-theme)))) - toggle-japanese-layout + toggle-text-layout (mf/use-fn - (mf/deps japanese-layout-enabled) - (fn [event] + (mf/deps layout-config) + (fn [language event] (dom/stop-propagation event) - (st/emit! (dw/set-japanese-layout (not japanese-layout-enabled))) + (st/emit! (dw/set-text-layout language (not (get-in layout-config [language :file?])))) (reset! show-menu* false) (reset! selected-sub-menu* nil))) @@ -1175,13 +1193,12 @@ :preferences [:> preferences-menu* {:layout layout :profile profile - :japanese-layout-enabled japanese-layout-enabled - :japanese-layout-all-files (:all-files? japanese-layout) + :layout-config layout-config :toggle-flag toggle-flag :toggle-theme toggle-theme :toggle-render toggle-render :show-shortcuts show-shortcuts - :toggle-japanese-layout toggle-japanese-layout + :toggle-text-layout toggle-text-layout :on-close close-sub-menu}] :plugins diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index ffbf56784d..513efbc884 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -32,6 +32,7 @@ [app.main.ui.hooks :as hooks] [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-language-layout :as tll] [app.main.ui.workspace.sidebar.options.menus.token-typography-row :refer [token-typography-row*]] [app.main.ui.workspace.sidebar.options.menus.typography :refer [text-options* typography-entry*]] [app.main.ui.workspace.tokens.management.forms.controls.utils :as csu] @@ -39,6 +40,7 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.object :as obj] [app.util.text.content :as content] + [app.util.text.layout :as tl] [app.util.text.ui :as txu] [app.util.text.writing-mode :as wm] [app.util.timers :as ts] @@ -314,6 +316,10 @@ (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) @@ -355,6 +361,26 @@ ;; Main component ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(mf/defc language-layout-section* + [{:keys [language title config options-props]}] + (let [open* (mf/use-state true) + open? (deref open*) + toggle-open (mf/use-fn #(swap! open* not))] + (when (tl/controls-enabled? config) + [:section {:class (stl/css :element-set) + :data-keep-editing-on-blur true + :aria-label title} + [:div {:class (stl/css :element-title)} + [:> title-bar* {:collapsable true + :collapsed (not open?) + :on-collapsed toggle-open + :title title + :class (stl/css :title-spacing-text)}]] + (when open? + [:div {:class (stl/css :element-content)} + [:> tll/language-layout-options* + (mf/spread-props options-props {:language language})]])]))) + (mf/defc text-menu* {::mf/wrap [#(mf/memo' % check-props)]} [{:keys [ids type values ruby-values text-selection-active @@ -362,15 +388,11 @@ (let [;; --- UI state menu-state* (mf/use-state {:main-menu true - :more-options false - :japanese-layout true}) + :more-options false}) menu-state (deref menu-state*) main-menu-open? (:main-menu menu-state) more-options-open? (:more-options menu-state) - japanese-layout-open? (:japanese-layout menu-state) - - japanese-layout-config-enabled? - (tjl/japanese-layout-config-enabled? (mf/deref refs/japanese-layout-config)) + layout-config (mf/deref refs/text-layout-config) font-id (or (:font-id values) (:font-id txt/default-typography)) @@ -461,10 +483,6 @@ (mf/use-fn #(swap! menu-state* update :more-options not)) - toggle-japanese-layout - (mf/use-fn - #(swap! menu-state* update :japanese-layout not)) - toggle-token-dropdown (mf/use-fn #(swap! token-dropdown-open* not)) @@ -563,7 +581,7 @@ :show-recent true :on-blur on-text-blur}) - japanese-layout-props + language-layout-props (mf/props {:ids ids :values values @@ -683,17 +701,15 @@ [:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})] [:> text-direction-options* common-props]])])] - (when japanese-layout-config-enabled? - [:section {:class (stl/css :element-set) - ;; Focusing these controls must not exit the v3 text editor (see `keep-editing-on-blur?`). - :data-keep-editing-on-blur true - :aria-label (tr "workspace.options.text-options.japanese-layout")} - [:div {:class (stl/css :element-title)} - [:> title-bar* {:collapsable true - :collapsed (not japanese-layout-open?) - :on-collapsed toggle-japanese-layout - :title (tr "workspace.options.text-options.japanese-layout") - :class (stl/css :title-spacing-text)}]] - (when japanese-layout-open? - [:div {:class (stl/css :element-content)} - [:> tjl/japanese-layout-options* japanese-layout-props]])])])) + [:> language-layout-section* {:language :japanese + :title (tr "workspace.options.text-options.japanese-layout") + :config (:japanese layout-config) + :options-props language-layout-props}] + [:> language-layout-section* {:language :chinese + :title (tr "workspace.options.text-options.chinese-layout") + :config (:chinese layout-config) + :options-props language-layout-props}] + [:> language-layout-section* {:language :korean + :title (tr "workspace.options.text-options.korean-layout") + :config (:korean layout-config) + :options-props language-layout-props}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs index 441fa3a41e..dbfdb71f41 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs @@ -18,7 +18,6 @@ [app.main.ui.workspace.sidebar.options.common :refer [advanced-options* radio-selected]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] - [app.util.text.writing-mode :as wm] [rumext.v2 :as mf])) (def ^:private mixed-span-values @@ -49,14 +48,6 @@ :disabled true :dimmed true}))) -(defn japanese-layout-config-enabled? - "True under the WASM renderer when Japanese layout is enabled for the file - or in the user's profile. `config` is a `refs/japanese-layout-config` - value." - [{:keys [file? all-files?]}] - (and ^boolean (wm/vertical-layout-active?) - (boolean (or file? all-files?)))) - (defn vertical-japanese-layout? "True when the Japanese layout controls are editing vertical text." [values] @@ -93,6 +84,10 @@ "Radio group bound to one text attribute." [{:keys [values on-change on-blur attr default-value name class options]}] (let [selected (radio-selected (get values attr) default-value) + group-id (mf/use-id) + options (mf/with-memo [options group-id] + (mapv #(update % :id (fn [id] (str group-id "-" id))) options)) + name (str group-id "-" name) handle-change (mf/use-fn (mf/deps attr on-change on-blur) @@ -127,6 +122,24 @@ :label (translate "workspace.options.text-options.text-orientation-upright") :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)} + [:> 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) + [:> 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)})])])) + ;; Warichu (割注): renders the selection as two half-size lines in one inline ;; position (top/bottom horizontally, right/left vertically). (defn warichu-options diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs new file mode 100644 index 0000000000..2dc175b2b4 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs @@ -0,0 +1,69 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.main.ui.workspace.sidebar.options.menus.text-language-layout + (:require-macros [app.main.style :as stl]) + (:require + [app.main.ui.ds.controls.select :refer [select*]] + [app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl] + [app.util.i18n :refer [tr]] + [app.util.text.layout :as tl] + [rumext.v2 :as mf])) + +(defn profile-options + [language value translate] + (let [options (into [{:id "" :label (translate "workspace.options.text-options.profile-plain")}] + (case language + :japanese [{:id "ja" :label (translate "workspace.options.text-options.profile-japanese")}] + :chinese [{:id "zh-Hans" :label (translate "workspace.options.text-options.profile-chinese-simplified")} + {:id "zh-Hant" :label (translate "workspace.options.text-options.profile-chinese-traditional")}] + :korean [{:id "ko" :label (translate "workspace.options.text-options.profile-korean")}])) + mixed? (= :multiple value) + id (if mixed? "multiple" (or value ""))] + (cond-> options + (not (some #(= (:id %) id) options)) + (conj {:id id + :label (if mixed? (translate "labels.mixed-values") value) + :disabled true + :dimmed true})))) + +(defn profile-change + [value] + {:text-language (when (seq value) value)}) + +(mf/defc language-layout-options* + [{:keys [language values ruby-values text-selection-active + on-change on-ruby-presentation-change on-blur]}] + (let [profile (:text-language values) + selected (if (= :multiple profile) "multiple" (or profile "")) + options (mf/with-memo [language profile] (profile-options language profile tr)) + matches? (tl/profile-matches? language profile) + handle-profile-change (mf/use-fn + (mf/deps on-change) + #(on-change (profile-change %))) + props (mf/props {:values values :on-change on-change :on-blur on-blur})] + [:div {:class (stl/css :language-layout-options)} + [:div {:class (stl/css :profile-option)} + [:span {:class (stl/css :option-label)} + (tr "workspace.options.text-options.language-profile")] + [:> select* {:default-selected selected + :aria-label (tr "workspace.options.text-options.language-profile") + :options options + :on-change handle-profile-change}]] + (if (and (= :japanese language) matches?) + [:> 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})] + [:> tjl/writing-mode-controls* props]) + (when (and (= :korean language) matches?) + [:> tjl/span-select-option* + (mf/spread-props props + {:attr :korean-wrap + :default-value "syllable" + :label (tr "workspace.options.text-options.korean-wrap") + :options [{:id "syllable" :label (tr "workspace.options.text-options.korean-wrap-syllable")} + {:id "spaces-only" :label (tr "workspace.options.text-options.korean-wrap-spaces-only")}]})])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.scss new file mode 100644 index 0000000000..3a7b62bfdc --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.scss @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC Sucursal en España SL + +@use "ds/typography.scss" as t; + +.language-layout-options { + display: flex; + flex-direction: column; + gap: var(--sp-xs); + padding-block: var(--sp-xs); +} + +.profile-option { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); + align-items: center; + gap: var(--sp-s); +} + +.option-label { + @include t.use-typography("body-small"); + + color: var(--color-foreground-secondary); +} diff --git a/frontend/src/app/render_wasm/text_editor.cljs b/frontend/src/app/render_wasm/text_editor.cljs index 6307e5f184..be7813554f 100644 --- a/frontend/src/app/render_wasm/text_editor.cljs +++ b/frontend/src/app/render_wasm/text_editor.cljs @@ -808,13 +808,36 @@ {} spans))))) +(defn selection-paragraph-styles + "Profile 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) + paragraphs (-> content :children first :children)] + (reduce + (fn [result paragraph] + (reduce + (fn [result attr] + (let [value (get paragraph attr)] + (assoc result attr + (if (and (contains? result attr) + (not= (get result attr) value)) + :multiple + value)))) + result + [:text-language :korean-wrap])) + {} + (subvec paragraphs start-para (inc end-para)))))) + (defn text-editor-get-current-japanese-styles - "Japanese span styles of the active WASM editor selection." + "Japanese span styles and paragraph profiles of the active editor selection." [] (when (wasm/ready?) (let [shape-id (text-editor-get-active-shape-id) - selection (text-editor-get-selection)] - (selection-japanese-styles (get-cached-content shape-id) selection)))) + selection (text-editor-get-selection) + content (get-cached-content shape-id)] + (merge (selection-japanese-styles content selection) + (selection-paragraph-styles content selection))))) (defn apply-attrs-to-paragraph "Apply `styles` (attrs map, or a fn per span) within [sel-start, sel-end), splitting spans." diff --git a/frontend/src/app/util/text/layout.cljs b/frontend/src/app/util/text/layout.cljs new file mode 100644 index 0000000000..c6d1d8282e --- /dev/null +++ b/frontend/src/app/util/text/layout.cljs @@ -0,0 +1,30 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns app.util.text.layout + (:require + [app.util.text.writing-mode :as wm] + [clojure.string :as str])) + +(defn controls-enabled? + [{:keys [feature-enabled file? all-files?]}] + (and (true? feature-enabled) + ^boolean (wm/vertical-layout-active?) + (boolean (or file? all-files?)))) + +(defn file-action-visible? + [{:keys [feature-enabled all-files?]} can-edit? read-only?] + (and (true? feature-enabled) + ^boolean (wm/vertical-layout-active?) + (true? can-edit?) + (not read-only?) + (not all-files?))) + +(defn profile-matches? + [language text-language] + (and (string? text-language) + (= (case language :japanese "ja" :chinese "zh" :korean "ko") + (first (str/split (str/lower-case text-language) #"-"))))) diff --git a/frontend/test/frontend_tests/render_wasm/texts_test.cljs b/frontend/test/frontend_tests/render_wasm/texts_test.cljs index fa7fc49224..e688822f0c 100644 --- a/frontend/test/frontend_tests/render_wasm/texts_test.cljs +++ b/frontend/test/frontend_tests/render_wasm/texts_test.cljs @@ -81,6 +81,22 @@ (text-editor/selection-japanese-styles content (selection 0 1 0 2)))))) +(t/deftest paragraph-profiles-follow-the-editor-selection + (let [content {:type "root" + :children [{:type "paragraph-set" + :children [{:type "paragraph" :children [{:text "Plain"}]} + {:type "paragraph" :text-language "ko" + :korean-wrap "spaces-only" + :children [{:text "한국어"}]}]}]}] + (t/is (= {:text-language nil :korean-wrap nil} + (text-editor/selection-paragraph-styles content (selection 0 0 0 0)))) + (t/is (= {:text-language "ko" :korean-wrap "spaces-only"} + (text-editor/selection-paragraph-styles content (selection 1 1 1 1)))) + (t/is (= {:text-language :multiple :korean-wrap :multiple} + (text-editor/selection-paragraph-styles content (selection 0 0 1 1)))) + (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 (let [content (editor-content [{:text "平成" diff --git a/frontend/test/frontend_tests/ui/text_options_test.cljs b/frontend/test/frontend_tests/ui/text_options_test.cljs index e94f233f85..d414491639 100644 --- a/frontend/test/frontend_tests/ui/text_options_test.cljs +++ b/frontend/test/frontend_tests/ui/text_options_test.cljs @@ -16,6 +16,8 @@ [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-language-layout :as tll] + [app.util.text.layout :as tl] [app.util.text.writing-mode :as wm] [cljs.test :as t :include-macros true] [cuerdas.core :as str] @@ -35,14 +37,75 @@ :annotation-clearance]) -(t/deftest japanese-layout-controls-follow-file-or-profile-configuration +(t/deftest layout-controls-follow-file-or-profile-configuration (with-redefs [wm/vertical-layout-active? (constantly true)] - (t/is (false? (tjl/japanese-layout-config-enabled? {}))) - (t/is (false? (tjl/japanese-layout-config-enabled? {:file? false :all-files? false}))) - (t/is (true? (tjl/japanese-layout-config-enabled? {:file? true :all-files? false}))) - (t/is (true? (tjl/japanese-layout-config-enabled? {:file? false :all-files? true})))) + (t/is (false? (tl/controls-enabled? {}))) + (t/is (false? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? false}))) + (t/is (true? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? false}))) + (t/is (true? (tl/controls-enabled? {:feature-enabled true :file? false :all-files? true})))) (with-redefs [wm/vertical-layout-active? (constantly false)] - (t/is (false? (tjl/japanese-layout-config-enabled? {:file? true :all-files? true}))))) + (t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true :all-files? true}))))) + +(t/deftest language-controls-and-menu-actions-respect-independent-preferences + (with-redefs [wm/vertical-layout-active? (constantly true)] + (doseq [file? [true false] all-files? [true false]] + (let [config {:feature-enabled true :file? file? :all-files? all-files?}] + (t/is (= (or file? all-files?) (tl/controls-enabled? config))) + (t/is (= (not all-files?) (tl/file-action-visible? config true false))) + (t/is (false? (tl/file-action-visible? config false false))) + (t/is (false? (tl/file-action-visible? config true true))) + (t/is (false? (tl/controls-enabled? (assoc config :feature-enabled false))))))) + (with-redefs [wm/vertical-layout-active? (constantly false)] + (t/is (false? (tl/controls-enabled? {:feature-enabled true :file? true}))) + (t/is (false? (tl/file-action-visible? {:feature-enabled true :file? true} true false))))) + +(t/deftest choosing-a-profile-only-changes-the-paragraph-language + (t/is (= {:text-language "ko"} (tll/profile-change "ko"))) + (t/is (= {:text-language nil} (tll/profile-change ""))) + (t/is (tl/profile-matches? :japanese "ja-JP")) + (t/is (tl/profile-matches? :chinese "zh-Hant-TW")) + (t/is (tl/profile-matches? :korean "KO-kr")) + (t/is (not (tl/profile-matches? :chinese "ja"))) + (t/is (not (tl/profile-matches? :korean :multiple))) + (t/is (not (tl/profile-matches? :japanese nil)))) + +(t/deftest profile-choices-keep-mixed-and-other-language-values-visible + (let [mixed (last (tll/profile-options :korean :multiple identity)) + other (last (tll/profile-options :chinese "ja" identity))] + (t/is (= {:id "multiple" :label "labels.mixed-values" :disabled true :dimmed true} mixed)) + (t/is (= {:id "ja" :label "ja" :disabled true :dimmed true} other)) + (t/is (= ["" "zh-Hans" "zh-Hant"] + (mapv :id (tll/profile-options :chinese nil identity)))))) + +(t/deftest language-specific-controls-require-an-explicit-matching-profile + (with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))] + (doseq [[language attr label] [[:japanese "ja" "workspace.options.text-options.line-adjustment"] + [:korean "ko" "workspace.options.text-options.korean-wrap"]] + profile [nil :multiple "zh-Hans" attr]] + (let [changes (atom []) + markup (rds/renderToStaticMarkup + (mf/element tll/language-layout-options* + #js {:language language + :values {:text-language profile} + :on-change #(swap! changes conj %)}))] + (t/is (= (= profile attr) (str/includes? markup label))) + (t/is (str/includes? markup "horizontal-tb-writing-mode")) + (t/is (empty? @changes) "Showing controls must not assign a profile"))))) + +(t/deftest shared-writing-mode-controls-have-independent-radio-groups + (with-redefs [hooks/use-portal-container (fn ([] nil) ([_] nil))] + (let [markup (rds/renderToStaticMarkup + (mf/html + [:div + [:> tll/language-layout-options* {:language :japanese :values {} :on-change identity}] + [:> tll/language-layout-options* {:language :chinese :values {} :on-change identity}] + [:> tll/language-layout-options* {:language :korean :values {} :on-change identity}]])) + inputs (re-seq #"]+>" markup) + ids (map #(second (re-find #"id=\"([^\"]+)\"" %)) inputs) + names (map #(second (re-find #"name=\"([^\"]+)\"" %)) inputs)] + (t/is (= 6 (count (set ids)))) + (t/is (= 3 (count (set names)))) + (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) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index e15aa08092..ae86ef2b30 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -9945,3 +9945,79 @@ msgstr "" #: src/app/main/ui/workspace/sidebar/versions.cljs:462 msgid "workspace.versions.warning.text" msgstr "Autosaved versions will be kept for %s days." + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.text-layout.title" +msgstr "Text layout by language" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.text-layout.description" +msgstr "Choose which text layout controls are available in all files." + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.chinese-layout.enable-all-files" +msgstr "Enable Chinese text layout for all files" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.korean-layout.enable-all-files" +msgstr "Enable Korean text layout for all files" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.enable-chinese-layout" +msgstr "Enable Chinese text layout" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.disable-chinese-layout" +msgstr "Disable Chinese text layout" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.enable-korean-layout" +msgstr "Enable Korean text layout" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.disable-korean-layout" +msgstr "Disable Korean text layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.chinese-layout" +msgstr "Chinese text layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.korean-layout" +msgstr "Korean text layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.language-profile" +msgstr "Language profile" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-plain" +msgstr "No language profile" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-japanese" +msgstr "Japanese" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-chinese-simplified" +msgstr "Simplified Chinese" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-chinese-traditional" +msgstr "Traditional Chinese" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-korean" +msgstr "Korean" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap" +msgstr "Line wrapping" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap-syllable" +msgstr "By syllable" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap-spaces-only" +msgstr "At spaces (experimental)" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6d20e94fe9..bff81c319d 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -9821,3 +9821,79 @@ msgstr "Si quieres aumentar este límite, contáctanos en [support@penpot.app](% #: src/app/main/ui/workspace/sidebar/versions.cljs:462 msgid "workspace.versions.warning.text" msgstr "Los autoguardados duran %s días." + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.text-layout.title" +msgstr "Disposición del texto por idioma" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.text-layout.description" +msgstr "Elige qué controles de disposición del texto están disponibles en todos los archivos." + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.chinese-layout.enable-all-files" +msgstr "Activar la disposición del texto chino en todos los archivos" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.korean-layout.enable-all-files" +msgstr "Activar la disposición del texto coreano en todos los archivos" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.enable-chinese-layout" +msgstr "Activar la disposición del texto chino" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.disable-chinese-layout" +msgstr "Desactivar la disposición del texto chino" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.enable-korean-layout" +msgstr "Activar la disposición del texto coreano" + +#: src/app/main/ui/workspace/main_menu.cljs +msgid "workspace.header.menu.disable-korean-layout" +msgstr "Desactivar la disposición del texto coreano" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.chinese-layout" +msgstr "Disposición del texto chino" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.korean-layout" +msgstr "Disposición del texto coreano" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.language-profile" +msgstr "Perfil de idioma" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-plain" +msgstr "Sin perfil de idioma" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-japanese" +msgstr "Japonés" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-chinese-simplified" +msgstr "Chino simplificado" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-chinese-traditional" +msgstr "Chino tradicional" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.profile-korean" +msgstr "Coreano" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap" +msgstr "Salto de línea" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap-syllable" +msgstr "Por sílaba" + +#: src/app/main/ui/workspace/sidebar/options/menus/text_language_layout.cljs +msgid "workspace.options.text-options.korean-wrap-spaces-only" +msgstr "En los espacios (experimental)"