feat(polish): add configurable style packs
Add shared prompt composition, custom style management, iCloud sync, and native iOS/macOS selection interfaces so users can keep a consistent writing voice across devices.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// MinimalTabBar.swift
|
||||
// OSGKeyboard · Main App
|
||||
//
|
||||
// Bottom tab bar — four icons, no labels.
|
||||
// Bottom tab bar — five icons, no labels.
|
||||
// Capsule uses iOS 26 Liquid Glass (.regular.interactive) so content
|
||||
// behind the dock refracts through on scroll.
|
||||
|
||||
@@ -12,6 +12,7 @@ enum AppTab: Int, CaseIterable {
|
||||
case keyboard
|
||||
case history
|
||||
case dictionary
|
||||
case styles
|
||||
case settings
|
||||
|
||||
var icon: MaterialIconName {
|
||||
@@ -19,6 +20,7 @@ enum AppTab: Int, CaseIterable {
|
||||
case .keyboard: return .keyboard
|
||||
case .history: return .menuBook
|
||||
case .dictionary: return .menuBook // unused — dictionary uses SF Symbol
|
||||
case .styles: return .menuBook // unused — styles uses SF Symbol
|
||||
case .settings: return .settings
|
||||
}
|
||||
}
|
||||
@@ -27,6 +29,7 @@ enum AppTab: Int, CaseIterable {
|
||||
var sfSymbol: String? {
|
||||
switch self {
|
||||
case .dictionary: return "square.stack.3d.down.right.fill"
|
||||
case .styles: return "text.badge.star"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
@@ -36,6 +39,7 @@ enum AppTab: Int, CaseIterable {
|
||||
case .keyboard: return "tab.keyboard"
|
||||
case .history: return "tab.history"
|
||||
case .dictionary: return "tab.dictionary"
|
||||
case .styles: return "tab.styles"
|
||||
case .settings: return "tab.settings"
|
||||
}
|
||||
}
|
||||
@@ -48,6 +52,7 @@ enum AppTab: Int, CaseIterable {
|
||||
case .keyboard: return "house"
|
||||
case .history: return "clock.arrow.circlepath"
|
||||
case .dictionary: return "character.book.closed"
|
||||
case .styles: return "text.badge.star"
|
||||
case .settings: return "gearshape"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ struct HistoryView: View {
|
||||
.textCase(.uppercase)
|
||||
.tracking(0.5)
|
||||
}
|
||||
.listSectionMargins(.horizontal, Spacing.lg)
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
|
||||
@@ -18,6 +18,8 @@ struct MainTabContent: View {
|
||||
HistoryView()
|
||||
case .dictionary:
|
||||
PersonalDictionaryView()
|
||||
case .styles:
|
||||
PolishStylesView()
|
||||
case .settings:
|
||||
SettingsView(presentation: .tab)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
// PolishStylesView.swift
|
||||
// OSGKeyboard · Main App
|
||||
//
|
||||
// Main-app editor for complete polish writing personalities. The keyboard
|
||||
// reads the selected pack from App Group storage on the next polish request.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
|
||||
@MainActor
|
||||
struct PolishStylesView: View {
|
||||
@Environment(\.themePalette) private var palette
|
||||
@ObservedObject private var config = ProviderConfig.shared
|
||||
|
||||
@State private var catalog = AppGroupStore().polishStyleCatalog
|
||||
@State private var activeID = AppGroupStore().activePolishStyleId
|
||||
@State private var editingPack: PolishStylePack?
|
||||
@State private var viewingPack: PolishStylePack?
|
||||
@State private var showEditor = false
|
||||
@State private var errorMessage: String?
|
||||
|
||||
private let store = AppGroupStore()
|
||||
private let columns = [
|
||||
GridItem(.flexible(), spacing: Spacing.md),
|
||||
GridItem(.flexible(), spacing: Spacing.md),
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: Spacing.xl) {
|
||||
packGridSection(
|
||||
title: "polishStyles.builtin.section",
|
||||
packs: PolishStylePackCatalog.builtins
|
||||
)
|
||||
if !catalog.entries.isEmpty {
|
||||
packGridSection(
|
||||
title: "polishStyles.custom.section",
|
||||
packs: PolishStylePackCatalog.all(userCatalog: catalog)
|
||||
.filter { $0.kind == .user }
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Spacing.md)
|
||||
.padding(.top, Spacing.sm)
|
||||
.padding(.bottom, Spacing.xl)
|
||||
}
|
||||
.background(palette.background)
|
||||
.tabBarScrollBottomPadding()
|
||||
.navigationTitle("polishStyles.title")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
editingPack = nil
|
||||
showEditor = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.disabled(catalog.entries.count >= PolishStyleLimits.maximumUserPacks)
|
||||
.accessibilityLabel(Text("polishStyles.add"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showEditor) {
|
||||
PolishStyleEditorSheet(pack: editingPack) { pack in
|
||||
save(pack)
|
||||
}
|
||||
}
|
||||
.sheet(item: $viewingPack) { pack in
|
||||
PolishStylePromptDetailSheet(pack: pack, language: config.uiLanguage)
|
||||
}
|
||||
.alert(
|
||||
Text("polishStyles.error.title"),
|
||||
isPresented: Binding(
|
||||
get: { errorMessage != nil },
|
||||
set: { if !$0 { errorMessage = nil } }
|
||||
)
|
||||
) {
|
||||
Button("common.done") { errorMessage = nil }
|
||||
} message: {
|
||||
Text(errorMessage ?? "")
|
||||
}
|
||||
.task {
|
||||
reload()
|
||||
await PolishStyleCloudSync.shared.pullAndMergeIfEnabled()
|
||||
reload()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .polishStylesDidSyncFromCloud)) { _ in
|
||||
reload()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .settingsDidSyncFromCloud)) { _ in
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
private func packGridSection(
|
||||
title: LocalizedStringKey,
|
||||
packs: [PolishStylePack]
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: Spacing.sm) {
|
||||
Text(title)
|
||||
.font(TypeStyle.caption2)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.textCase(.uppercase)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
LazyVGrid(columns: columns, spacing: Spacing.md) {
|
||||
ForEach(packs) { pack in
|
||||
packCard(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func packCard(_ pack: PolishStylePack) -> some View {
|
||||
let isSelected = pack.id == activeID
|
||||
return ZStack(alignment: .topTrailing) {
|
||||
Button {
|
||||
activate(pack)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: Spacing.sm) {
|
||||
Image(systemName: iconName(for: pack))
|
||||
.font(.system(size: 24, weight: .medium))
|
||||
.foregroundStyle(isSelected ? palette.accent : palette.textSecondary)
|
||||
Text(pack.displayName(language: config.uiLanguage))
|
||||
.font(TypeStyle.body)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
.lineLimit(1)
|
||||
Text(descriptionKey(for: pack))
|
||||
.font(TypeStyle.caption2)
|
||||
.foregroundStyle(palette.textTertiary)
|
||||
.lineLimit(3)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 132, alignment: .leading)
|
||||
.padding(Spacing.md)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Button {
|
||||
if pack.kind == .builtin {
|
||||
viewingPack = pack
|
||||
} else {
|
||||
editingPack = pack
|
||||
showEditor = true
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: pack.kind == .builtin ? "eye" : "pencil")
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(palette.background.opacity(0.75), in: Circle())
|
||||
}
|
||||
.padding(Spacing.sm)
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(
|
||||
Text(pack.kind == .builtin ? "polishStyles.viewPrompt" : "polishStyles.edit")
|
||||
)
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 21, weight: .semibold))
|
||||
.foregroundStyle(palette.accent)
|
||||
.background(Color.white, in: Circle())
|
||||
.padding(Spacing.sm)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
isSelected ? palette.accentMuted : palette.surface,
|
||||
in: RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
|
||||
.stroke(
|
||||
isSelected ? palette.accent : palette.divider,
|
||||
lineWidth: isSelected ? 1.5 : 0.5
|
||||
)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Radius.xl, style: .continuous))
|
||||
.contextMenu {
|
||||
Button("polishStyles.duplicate") {
|
||||
duplicate(pack)
|
||||
}
|
||||
if pack.kind == .user {
|
||||
Button("common.delete", role: .destructive) {
|
||||
delete(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func iconName(for pack: PolishStylePack) -> String {
|
||||
switch pack.id {
|
||||
case "builtin.structured": return "list.bullet.rectangle"
|
||||
case "builtin.formal": return "briefcase"
|
||||
case "builtin.dating": return "heart.text.square"
|
||||
case "builtin.chat": return "bubble.left.and.bubble.right"
|
||||
case "builtin.light": return "wand.and.sparkles"
|
||||
default: return "text.badge.star"
|
||||
}
|
||||
}
|
||||
|
||||
private func descriptionKey(for pack: PolishStylePack) -> LocalizedStringKey {
|
||||
guard pack.kind == .builtin else { return "polishStyles.custom.description" }
|
||||
switch pack.id {
|
||||
case "builtin.structured": return "polishStyles.structured.description"
|
||||
case "builtin.formal": return "polishStyles.formal.description"
|
||||
case "builtin.dating": return "polishStyles.dating.description"
|
||||
case "builtin.chat": return "polishStyles.chat.description"
|
||||
default: return "polishStyles.light.description"
|
||||
}
|
||||
}
|
||||
|
||||
private func reload() {
|
||||
catalog = store.polishStyleCatalog
|
||||
activeID = store.activePolishStyleId
|
||||
}
|
||||
|
||||
private func activate(_ pack: PolishStylePack) {
|
||||
store.setActivePolishStyleId(pack.id)
|
||||
activeID = pack.id
|
||||
Task {
|
||||
try? await AppCloudSync.shared.settingsSyncService.pushLocalIfEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
private func save(_ pack: PolishStylePack) {
|
||||
do {
|
||||
try catalog.upsert(pack)
|
||||
store.setPolishStyleCatalog(catalog)
|
||||
store.setActivePolishStyleId(pack.id)
|
||||
activeID = pack.id
|
||||
Task {
|
||||
try? await PolishStyleCloudSync.shared.pushLocalIfEnabled(catalog)
|
||||
try? await AppCloudSync.shared.settingsSyncService.pushLocalIfEnabled()
|
||||
}
|
||||
} catch {
|
||||
errorMessage = localized(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func duplicate(_ pack: PolishStylePack) {
|
||||
guard catalog.entries.count < PolishStyleLimits.maximumUserPacks else {
|
||||
errorMessage = AppL10n.string("polishStyles.error.limit")
|
||||
return
|
||||
}
|
||||
editingPack = PolishStylePack(
|
||||
name: String(
|
||||
format: AppL10n.string("polishStyles.copyName"),
|
||||
pack.displayName(language: config.uiLanguage)
|
||||
),
|
||||
prompt: pack.prompt
|
||||
)
|
||||
showEditor = true
|
||||
}
|
||||
|
||||
private func delete(_ pack: PolishStylePack) {
|
||||
guard pack.kind == .user else { return }
|
||||
catalog.recordDeletion(of: pack.id)
|
||||
store.setPolishStyleCatalog(catalog)
|
||||
if activeID == pack.id {
|
||||
activeID = PolishStylePackCatalog.defaultID
|
||||
store.setActivePolishStyleId(activeID)
|
||||
}
|
||||
Task {
|
||||
try? await PolishStyleCloudSync.shared.pushLocalIfEnabled(catalog)
|
||||
try? await AppCloudSync.shared.settingsSyncService.pushLocalIfEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
private func localized(_ error: Error) -> String {
|
||||
switch error as? PolishStyleValidationError {
|
||||
case .emptyName: return AppL10n.string("polishStyles.error.emptyName")
|
||||
case .emptyPrompt: return AppL10n.string("polishStyles.error.emptyPrompt")
|
||||
case .tooManyUserPacks: return AppL10n.string("polishStyles.error.limit")
|
||||
case .promptTooLong: return AppL10n.string("polishStyles.error.promptTooLong")
|
||||
case .builtinIsImmutable: return AppL10n.string("polishStyles.error.builtin")
|
||||
case nil: return AppL10n.string("polishStyles.error.generic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PolishStylePromptDetailSheet: View {
|
||||
let pack: PolishStylePack
|
||||
let language: AppUILanguage
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.themePalette) private var palette
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
Text(pack.prompt)
|
||||
.font(.body.monospaced())
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
.textSelection(.enabled)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(Spacing.md)
|
||||
.background(
|
||||
palette.surface,
|
||||
in: RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
|
||||
.stroke(palette.divider, lineWidth: 0.5)
|
||||
)
|
||||
.padding(Spacing.md)
|
||||
}
|
||||
.background(palette.background)
|
||||
.navigationTitle(pack.displayName(language: language))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("common.done") { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PolishStyleEditorSheet: View {
|
||||
let pack: PolishStylePack?
|
||||
let onSave: (PolishStylePack) -> Void
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.themePalette) private var palette
|
||||
@State private var name: String
|
||||
@State private var prompt: String
|
||||
|
||||
init(pack: PolishStylePack?, onSave: @escaping (PolishStylePack) -> Void) {
|
||||
self.pack = pack
|
||||
self.onSave = onSave
|
||||
_name = State(initialValue: pack?.name ?? "")
|
||||
_prompt = State(initialValue: pack?.prompt ?? PolishStylePackCatalog.newUserPromptTemplate)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section("polishStyles.editor.name") {
|
||||
TextField("polishStyles.editor.namePlaceholder", text: $name)
|
||||
}
|
||||
Section {
|
||||
TextEditor(text: $prompt)
|
||||
.font(.body.monospaced())
|
||||
.frame(minHeight: 320)
|
||||
} header: {
|
||||
HStack {
|
||||
Text("polishStyles.editor.prompt")
|
||||
Spacer()
|
||||
Text("\(prompt.count)/\(PolishStyleLimits.maximumPromptCharacters)")
|
||||
.foregroundStyle(
|
||||
prompt.count > PolishStyleLimits.maximumPromptCharacters
|
||||
? palette.danger
|
||||
: palette.textTertiary
|
||||
)
|
||||
}
|
||||
} footer: {
|
||||
Text("polishStyles.editor.hint")
|
||||
}
|
||||
}
|
||||
.navigationTitle(pack == nil ? "polishStyles.add" : "polishStyles.edit")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("common.cancel") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("common.save") {
|
||||
let result = PolishStylePack(
|
||||
id: pack?.id ?? "user.\(UUID().uuidString.lowercased())",
|
||||
name: name,
|
||||
prompt: prompt,
|
||||
kind: .user,
|
||||
createdAt: pack?.createdAt ?? Date()
|
||||
)
|
||||
onSave(result)
|
||||
dismiss()
|
||||
}
|
||||
.disabled(
|
||||
name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
|| prompt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
|| prompt.count > PolishStyleLimits.maximumPromptCharacters
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ struct SettingsView: View {
|
||||
|
||||
private var dictionaryAndPolishSection: some View {
|
||||
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
|
||||
sectionHeader("settings.dictionaryAndPolish.title")
|
||||
sectionHeader("settings.polishPreferences.title")
|
||||
VStack(spacing: 0) {
|
||||
polishIntensityPreferenceRows
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
"settings.about.title" = "About";
|
||||
"settings.preferences.title" = "Preferences";
|
||||
"settings.dictionaryAndPolish.title" = "Dictionary & polish";
|
||||
"settings.polishPreferences.title" = "Polish preferences";
|
||||
"settings.handedness.title" = "Handedness";
|
||||
"settings.handedness.left" = "Left hand";
|
||||
"settings.handedness.right" = "Right hand";
|
||||
@@ -357,8 +358,38 @@
|
||||
"tab.keyboard" = "Keyboard";
|
||||
"tab.history" = "History";
|
||||
"tab.dictionary" = "Dictionary";
|
||||
"tab.styles" = "Styles";
|
||||
"tab.settings" = "Settings";
|
||||
|
||||
/* Polish style packs */
|
||||
"polishStyles.title" = "Polish styles";
|
||||
"polishStyles.add" = "Add style";
|
||||
"polishStyles.edit" = "Edit style";
|
||||
"polishStyles.viewPrompt" = "View full prompt";
|
||||
"polishStyles.duplicate" = "Duplicate";
|
||||
"polishStyles.builtin.section" = "Built-in";
|
||||
"polishStyles.custom.section" = "My styles";
|
||||
"polishStyles.intro.title" = "Choose a writing personality";
|
||||
"polishStyles.intro.body" = "The selected style shapes every polished dictation. Your custom styles sync through iCloud when settings sync is enabled.";
|
||||
"polishStyles.light.description" = "Fix recognition errors and punctuation with minimal rewriting.";
|
||||
"polishStyles.structured.description" = "Organize multiple points into clear paragraphs and lists.";
|
||||
"polishStyles.formal.description" = "Professional, restrained writing for email and work.";
|
||||
"polishStyles.dating.description" = "Warm, playful messages that invite conversation while respecting boundaries.";
|
||||
"polishStyles.chat.description" = "Short, natural messages without a formal tone.";
|
||||
"polishStyles.custom.description" = "Custom complete writing personality";
|
||||
"polishStyles.copyName" = "%@ Copy";
|
||||
"polishStyles.editor.name" = "Name";
|
||||
"polishStyles.editor.namePlaceholder" = "Style name";
|
||||
"polishStyles.editor.prompt" = "Complete prompt";
|
||||
"polishStyles.editor.hint" = "Use {{DICTIONARY}} where the personal dictionary should be inserted. System safety, rewrite intensity, and output rules are appended automatically.";
|
||||
"polishStyles.error.title" = "Couldn’t save style";
|
||||
"polishStyles.error.emptyName" = "Enter a style name.";
|
||||
"polishStyles.error.emptyPrompt" = "The prompt cannot be empty.";
|
||||
"polishStyles.error.limit" = "You can save up to 8 custom styles.";
|
||||
"polishStyles.error.promptTooLong" = "The prompt can contain up to 6,000 characters.";
|
||||
"polishStyles.error.builtin" = "Built-in styles cannot be changed. Duplicate one to customize it.";
|
||||
"polishStyles.error.generic" = "Try again.";
|
||||
|
||||
/* History */
|
||||
"history.title" = "History";
|
||||
"history.subtitle" = "Saved on this device only.";
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
"settings.about.title" = "关于";
|
||||
"settings.preferences.title" = "偏好设置";
|
||||
"settings.dictionaryAndPolish.title" = "词库与润色";
|
||||
"settings.polishPreferences.title" = "润色偏好";
|
||||
"settings.handedness.title" = "握持偏好";
|
||||
"settings.handedness.left" = "左手";
|
||||
"settings.handedness.right" = "右手";
|
||||
@@ -356,8 +357,38 @@
|
||||
"tab.keyboard" = "键盘";
|
||||
"tab.history" = "历史";
|
||||
"tab.dictionary" = "词库";
|
||||
"tab.styles" = "风格";
|
||||
"tab.settings" = "设置";
|
||||
|
||||
/* 润色风格包 */
|
||||
"polishStyles.title" = "润色风格";
|
||||
"polishStyles.add" = "添加风格";
|
||||
"polishStyles.edit" = "编辑风格";
|
||||
"polishStyles.viewPrompt" = "查看完整提示词";
|
||||
"polishStyles.duplicate" = "创建副本";
|
||||
"polishStyles.builtin.section" = "内置风格";
|
||||
"polishStyles.custom.section" = "我的风格";
|
||||
"polishStyles.intro.title" = "选择完整写作人格";
|
||||
"polishStyles.intro.body" = "选中的风格会影响每次听写润色。开启设置同步后,自定义风格会通过 iCloud 保存。";
|
||||
"polishStyles.light.description" = "修正识别错误与标点,尽量少改原话。";
|
||||
"polishStyles.structured.description" = "将多个事项整理为清晰段落与列表。";
|
||||
"polishStyles.formal.description" = "适合邮件和工作的专业、克制表达。";
|
||||
"polishStyles.dating.description" = "自然会撩、有温度,也尊重对方边界。";
|
||||
"polishStyles.chat.description" = "简短自然的聊天消息,避免公文腔。";
|
||||
"polishStyles.custom.description" = "自定义完整写作人格";
|
||||
"polishStyles.copyName" = "%@副本";
|
||||
"polishStyles.editor.name" = "名称";
|
||||
"polishStyles.editor.namePlaceholder" = "风格名称";
|
||||
"polishStyles.editor.prompt" = "完整提示词";
|
||||
"polishStyles.editor.hint" = "使用 {{DICTIONARY}} 指定个人词典的插入位置。系统会自动追加安全边界、润色力度和输出契约。";
|
||||
"polishStyles.error.title" = "无法保存风格";
|
||||
"polishStyles.error.emptyName" = "请输入风格名称。";
|
||||
"polishStyles.error.emptyPrompt" = "提示词不能为空。";
|
||||
"polishStyles.error.limit" = "最多可保存 8 个自定义风格。";
|
||||
"polishStyles.error.promptTooLong" = "提示词最多可输入 6,000 个字符。";
|
||||
"polishStyles.error.builtin" = "内置风格不能直接修改,请创建副本后自定义。";
|
||||
"polishStyles.error.generic" = "请重试。";
|
||||
|
||||
/* History */
|
||||
"history.title" = "历史";
|
||||
"history.subtitle" = "仅保存在本机。";
|
||||
|
||||
Reference in New Issue
Block a user