mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-08 09:37:47 +03:00
feat(desktop): spellcheck toggle and system-language dictionaries (#59129)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
a88d3ca0bc
commit
bf94de965d
@@ -1,3 +1,5 @@
|
||||
import { flow, Option, Schema } from "effect"
|
||||
|
||||
export const DESKTOP_NATIVE_LOCALES = [
|
||||
"en",
|
||||
"zh",
|
||||
@@ -302,6 +304,9 @@ export const DESKTOP_NATIVE_ENGLISH = {
|
||||
|
||||
"desktop.picker.error.notSelected": "File was not selected by the picker",
|
||||
"desktop.picker.error.sizeLimit": "Selected attachments exceed the {{limit}} MB limit",
|
||||
|
||||
"desktop.contextMenu.spellcheck": "Spellcheck",
|
||||
"desktop.contextMenu.spellcheckLanguages": "Spellcheck Languages",
|
||||
} as const
|
||||
|
||||
export type DesktopNativeKey = keyof typeof DESKTOP_NATIVE_ENGLISH
|
||||
@@ -310,6 +315,7 @@ export type DesktopNativeMessages = Record<DesktopNativeKey, string>
|
||||
|
||||
export type DesktopNativeBundle = { locale: DesktopNativeLocale; messages: DesktopNativeMessages }
|
||||
|
||||
// SAFETY: DESKTOP_NATIVE_ENGLISH is a const literal, so its own keys are exactly DesktopNativeKey.
|
||||
export const DESKTOP_NATIVE_KEYS = Object.keys(DESKTOP_NATIVE_ENGLISH) as DesktopNativeKey[]
|
||||
|
||||
export const DESKTOP_NATIVE_MAX_PAYLOAD_BYTES = 64 * 1024
|
||||
@@ -320,35 +326,24 @@ export function createDesktopNativeBundle(
|
||||
): DesktopNativeBundle {
|
||||
return {
|
||||
locale,
|
||||
// SAFETY: the entries map every DesktopNativeKey to a string, which is exactly DesktopNativeMessages.
|
||||
messages: Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key) => [key, translate(key)])) as DesktopNativeMessages,
|
||||
}
|
||||
}
|
||||
|
||||
export function parseDesktopNativeBundle(value: unknown): DesktopNativeBundle | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined
|
||||
const DesktopNativeBundleSchema = Schema.Struct({
|
||||
locale: Schema.Literals(DESKTOP_NATIVE_LOCALES),
|
||||
messages: Schema.Record(Schema.Literals(DESKTOP_NATIVE_KEYS), Schema.String),
|
||||
}).check(
|
||||
Schema.makeFilter(
|
||||
(bundle) => new TextEncoder().encode(JSON.stringify(bundle)).byteLength <= DESKTOP_NATIVE_MAX_PAYLOAD_BYTES,
|
||||
),
|
||||
)
|
||||
|
||||
try {
|
||||
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > DESKTOP_NATIVE_MAX_PAYLOAD_BYTES) return undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
const decodeDesktopNativeBundle = Schema.decodeUnknownOption(DesktopNativeBundleSchema, { onExcessProperty: "error" })
|
||||
|
||||
const bundle = value as { locale?: unknown; messages?: unknown }
|
||||
|
||||
if (!DESKTOP_NATIVE_LOCALES.some((locale) => locale === bundle.locale)) return undefined
|
||||
|
||||
if (!bundle.messages || typeof bundle.messages !== "object" || Array.isArray(bundle.messages)) return undefined
|
||||
const messages = bundle.messages as Record<string, unknown>
|
||||
const keys = Object.keys(messages)
|
||||
|
||||
if (keys.length !== DESKTOP_NATIVE_KEYS.length) return undefined
|
||||
|
||||
if (!DESKTOP_NATIVE_KEYS.every((key) => typeof messages[key] === "string")) return undefined
|
||||
|
||||
if (!keys.every((key) => key in DESKTOP_NATIVE_ENGLISH)) return undefined
|
||||
|
||||
return bundle as DesktopNativeBundle
|
||||
}
|
||||
/** Validates a bundle the renderer sent over IPC: a supported locale, exactly the native keys, and a bounded size. */
|
||||
export const parseDesktopNativeBundle = flow(decodeDesktopNativeBundle, Option.getOrUndefined)
|
||||
|
||||
export function formatDesktopNativeMessage(message: string, params?: Record<string, string | number>) {
|
||||
if (!params) return message
|
||||
|
||||
@@ -1005,6 +1005,8 @@ export const dict = {
|
||||
"settings.general.row.followUpBehavior.steer": "Steer",
|
||||
"settings.general.row.pinchZoom.title": "Pinch to zoom",
|
||||
"settings.general.row.pinchZoom.description": "Allow trackpad pinch and Ctrl-scroll gestures to zoom",
|
||||
"settings.general.row.spellcheck.title": "Spellcheck",
|
||||
"settings.general.row.spellcheck.description": "Underline misspelled words while typing",
|
||||
|
||||
"sound.option.none": "None",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
|
||||
@@ -72,7 +72,7 @@ const desktop = [
|
||||
]
|
||||
|
||||
export const dict = {
|
||||
...Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key, index) => [key, desktop[index]])),
|
||||
...Object.fromEntries(desktop.map((value, index) => [DESKTOP_NATIVE_KEYS[index], value])),
|
||||
"command.category.suggested": "Predloženo",
|
||||
"command.category.view": "Prikaz",
|
||||
"command.category.project": "Projekt",
|
||||
|
||||
@@ -72,7 +72,7 @@ const desktop = [
|
||||
]
|
||||
|
||||
export const dict = {
|
||||
...Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key, index) => [key, desktop[index]])),
|
||||
...Object.fromEntries(desktop.map((value, index) => [DESKTOP_NATIVE_KEYS[index], value])),
|
||||
"command.category.suggested": "Javasolt",
|
||||
"command.category.view": "Nézet",
|
||||
"command.category.project": "Projekt",
|
||||
|
||||
@@ -72,7 +72,7 @@ const desktop = [
|
||||
]
|
||||
|
||||
export const dict = {
|
||||
...Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key, index) => [key, desktop[index]])),
|
||||
...Object.fromEntries(desktop.map((value, index) => [DESKTOP_NATIVE_KEYS[index], value])),
|
||||
"command.category.suggested": "Tillögur",
|
||||
"command.category.view": "Skoða",
|
||||
"command.category.project": "Verkefni",
|
||||
|
||||
@@ -72,7 +72,7 @@ const desktop = [
|
||||
]
|
||||
|
||||
export const dict = {
|
||||
...Object.fromEntries(DESKTOP_NATIVE_KEYS.map((key, index) => [key, desktop[index]])),
|
||||
...Object.fromEntries(desktop.map((value, index) => [DESKTOP_NATIVE_KEYS[index], value])),
|
||||
"command.category.suggested": "Siūloma",
|
||||
"command.category.view": "Rodinys",
|
||||
"command.category.project": "Projektas",
|
||||
|
||||
@@ -108,6 +108,12 @@ type PlatformBase = {
|
||||
/** Allow native pinch/Ctrl-scroll zoom gestures (desktop only) */
|
||||
setPinchZoomEnabled?(enabled: boolean): Promise<void> | void
|
||||
|
||||
/** Get whether the native spellchecker underlines misspelled words (desktop only) */
|
||||
getSpellcheckEnabled?(): Promise<boolean>
|
||||
|
||||
/** Turn the native spellchecker on or off (desktop only) */
|
||||
setSpellcheckEnabled?(enabled: boolean): Promise<void>
|
||||
|
||||
/** Run a desktop-only menu action from the app chrome */
|
||||
runDesktopMenuAction?(action: DesktopMenuAction): Promise<void> | void
|
||||
|
||||
|
||||
@@ -249,6 +249,17 @@ export const SettingsGeneral: Component = () => {
|
||||
void update.catch(() => setPinchZoom(!checked))
|
||||
}
|
||||
|
||||
const [spellcheck, { mutate: setSpellcheck }] = createResource(
|
||||
() => desktop() && "getSpellcheckEnabled" in platform,
|
||||
() => platform.getSpellcheckEnabled?.().catch(() => true) ?? true,
|
||||
{ initialValue: true },
|
||||
)
|
||||
|
||||
const onSpellcheckChange = (checked: boolean) => {
|
||||
setSpellcheck(checked)
|
||||
void platform.setSpellcheckEnabled?.(checked).catch(() => setSpellcheck(!checked))
|
||||
}
|
||||
|
||||
const GeneralSection = () => (
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">{language.t("settings.general.section.general")}</h3>
|
||||
@@ -282,6 +293,14 @@ export const SettingsGeneral: Component = () => {
|
||||
<Switch checked={pinchZoom.latest} onChange={onPinchZoomChange} />
|
||||
</div>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.spellcheck.title")}
|
||||
description={language.t("settings.general.row.spellcheck.description")}
|
||||
>
|
||||
<div data-action="settings-spellcheck">
|
||||
<Switch checked={spellcheck.latest} onChange={onSpellcheckChange} />
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</Show>
|
||||
|
||||
<PluginSettingsSections page="general" section="general" />
|
||||
|
||||
@@ -55,6 +55,13 @@ export const clientSettings: Entry<SettingsRootTab>[] = [
|
||||
target: "settings-pinch-zoom",
|
||||
available: "desktop",
|
||||
},
|
||||
{
|
||||
tab: "general",
|
||||
label: "settings.general.row.spellcheck.title",
|
||||
target: "settings-spellcheck",
|
||||
keywords: "spelling spell check dictionary",
|
||||
available: "desktop",
|
||||
},
|
||||
{
|
||||
tab: "general",
|
||||
label: "settings.general.row.mobileTitlebarBottom.title",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { setForceFocus } from "../native/debug"
|
||||
import { showCliInstaller } from "../native/install-cli"
|
||||
import { DesktopLogging, scoped } from "../native/logging"
|
||||
import { createMenu, sendMenuCommand } from "../native/menu"
|
||||
import { getSpellcheckEnabled, setSpellcheckEnabled } from "../native/spellcheck"
|
||||
import { setNativeTranslations } from "../native/translations"
|
||||
import { IpcPortHandoff } from "../ipc-transport"
|
||||
import { ApplicationLifecycle } from "../lifecycle"
|
||||
@@ -37,6 +38,8 @@ export const appHandlers = AppRpcs.toLayer(
|
||||
AppCheckAppExists: ({ appName }) => checkAppExists(appName).pipe(Effect.orDie),
|
||||
AppResolveAppPath: ({ appName }) => resolveAppPath(appName).pipe(Effect.orDie),
|
||||
AppSetBackgroundColor: ({ color }) => Effect.sync(() => setBackgroundColor(color)),
|
||||
AppGetSpellcheckEnabled: () => Effect.sync(getSpellcheckEnabled),
|
||||
AppSetSpellcheckEnabled: ({ enabled }) => Effect.sync(() => setSpellcheckEnabled(enabled)),
|
||||
AppExportDebugLogs: () => logging.exportDebug,
|
||||
AppSetForceFocus: ({ enabled }, context) => promise(() => setForceFocus(sender(handoff, context), enabled)),
|
||||
AppRecordFatalRendererError: ({ error }) =>
|
||||
|
||||
@@ -6,12 +6,18 @@ import { DesktopPaths } from "../paths"
|
||||
import { getUserShell, loadShellEnv } from "../service/shell-env"
|
||||
import { registerRendererProtocol, setDockIcon, setProtocolReporter } from "../windows"
|
||||
import { scoped } from "../native/logging"
|
||||
import { applySpellcheck, spellcheckMenuItems } from "../native/spellcheck"
|
||||
|
||||
// electron-context-menu attaches to every existing and future window, so it can load once the first
|
||||
// window is up instead of holding up startup with its dependency tree.
|
||||
export const installContextMenu = Effect.gen(function* () {
|
||||
const { default: contextMenu } = yield* Effect.promise(() => import("electron-context-menu"))
|
||||
contextMenu({ showSaveImageAs: true, showLookUpSelection: false, showSearchWithGoogle: false })
|
||||
contextMenu({
|
||||
showSaveImageAs: true,
|
||||
showLookUpSelection: false,
|
||||
showSearchWithGoogle: false,
|
||||
append: (_actions, params) => (params.isEditable ? spellcheckMenuItems() : []),
|
||||
})
|
||||
})
|
||||
|
||||
export const prepareApplicationEnvironment = Effect.gen(function* () {
|
||||
@@ -45,12 +51,13 @@ export const prepareDesktop = Effect.gen(function* () {
|
||||
)
|
||||
registerRendererProtocol(paths.rendererRoot)
|
||||
setDockIcon(path, paths)
|
||||
applySpellcheck()
|
||||
})
|
||||
|
||||
export const loadProxyEnvironment = Effect.gen(function* () {
|
||||
yield* Effect.try(() => {
|
||||
ensureLoopbackNoProxy()
|
||||
// Electron 41.2 has a newer Node API than the current @types/node package.
|
||||
// SAFETY: Electron 41.2 ships a Node with http.setGlobalProxyFromEnv, which the current @types/node lacks.
|
||||
const proxyAwareHttp = http as typeof http & { setGlobalProxyFromEnv(): void }
|
||||
proxyAwareHttp.setGlobalProxyFromEnv()
|
||||
}).pipe(Effect.catch((error) => Effect.logWarning("failed to load proxy environment", { error })))
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { defaultSpellcheckLanguages } from "./spellcheck-languages"
|
||||
|
||||
// A slice of Electron 44's Linux and Windows list, including aliases such as de and de-DE.
|
||||
const available = ["de", "de-DE", "en", "en-AU", "en-GB", "en-US", "es", "es-MX", "fr", "fr-FR", "pt", "pt-BR"]
|
||||
|
||||
test.each([
|
||||
[["de-DE"], ["de-DE", "en-US"]],
|
||||
[
|
||||
["de-DE", "de", "en-US", "en"],
|
||||
["de-DE", "en-US"],
|
||||
],
|
||||
[
|
||||
["de-AT", "fr-CA"],
|
||||
["de", "fr", "en-US"],
|
||||
],
|
||||
[
|
||||
["en-GB", "de-DE"],
|
||||
["en-GB", "de-DE"],
|
||||
],
|
||||
[["en-NZ"], ["en"]],
|
||||
[
|
||||
["es-MX", "es-ES"],
|
||||
["es-MX", "en-US"],
|
||||
],
|
||||
[["pt-AO"], ["pt", "en-US"]],
|
||||
[["DE-de"], ["de-DE", "en-US"]],
|
||||
[["ja-JP"], ["en-US"]],
|
||||
[[], ["en-US"]],
|
||||
])("system languages %p use dictionaries %p", (preferred, languages) => {
|
||||
expect(defaultSpellcheckLanguages(available, preferred)).toEqual(languages)
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
const base = (tag: string) => tag.split("-")[0].toLowerCase()
|
||||
|
||||
// One dictionary per system language, matched exactly or by base language (de-AT -> de). Chromium
|
||||
// lists aliases such as de and de-DE separately, so a later tag for the same language is skipped.
|
||||
// US English is added when none of them is English, because prompts mix prose with English code terms.
|
||||
export function defaultSpellcheckLanguages(available: readonly string[], preferred: readonly string[]) {
|
||||
const find = (tag: string) => available.find((language) => language.toLowerCase() === tag.toLowerCase())
|
||||
|
||||
const languages = preferred
|
||||
.flatMap((tag) => find(tag) ?? find(base(tag)) ?? [])
|
||||
.filter((language, index, all) => all.findIndex((other) => base(other) === base(language)) === index)
|
||||
|
||||
if (languages.some((language) => base(language) === "en") || !available.includes("en-US")) return languages
|
||||
|
||||
return [...languages, "en-US"]
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { app, session, type MenuItemConstructorOptions } from "electron"
|
||||
import { SPELLCHECK_ENABLED_KEY, SPELLCHECK_LANGUAGES_KEY } from "../storage/keys"
|
||||
import { getStore } from "../storage/store"
|
||||
import { defaultSpellcheckLanguages } from "./spellcheck-languages"
|
||||
import { nativeLanguageOptions, nativeT } from "./translations"
|
||||
|
||||
// macOS checks spelling with the system spellchecker, which detects the language itself; Electron
|
||||
// ignores dictionary choices there.
|
||||
const choosesLanguages = process.platform !== "darwin"
|
||||
|
||||
export function applySpellcheck() {
|
||||
// Setting languages also turns the spellchecker on, so the stored on/off state goes last.
|
||||
if (choosesLanguages) session.defaultSession.setSpellCheckerLanguages(spellcheckLanguages())
|
||||
|
||||
session.defaultSession.setSpellCheckerEnabled(getSpellcheckEnabled())
|
||||
}
|
||||
|
||||
export function getSpellcheckEnabled() {
|
||||
return getStore().get(SPELLCHECK_ENABLED_KEY) !== false
|
||||
}
|
||||
|
||||
export function setSpellcheckEnabled(enabled: boolean) {
|
||||
getStore().set(SPELLCHECK_ENABLED_KEY, enabled)
|
||||
session.defaultSession.setSpellCheckerEnabled(enabled)
|
||||
}
|
||||
|
||||
// Mirrors the Spellcheck menu in chat apps: a checkbox, and a dictionary picker where Electron owns
|
||||
// the dictionaries.
|
||||
export function spellcheckMenuItems(): MenuItemConstructorOptions[] {
|
||||
const enabled = getSpellcheckEnabled()
|
||||
const current = session.defaultSession.getSpellCheckerLanguages()
|
||||
|
||||
const toggle: MenuItemConstructorOptions = {
|
||||
label: nativeT("desktop.contextMenu.spellcheck"),
|
||||
type: "checkbox",
|
||||
checked: enabled,
|
||||
click: () => setSpellcheckEnabled(!enabled),
|
||||
}
|
||||
|
||||
if (!choosesLanguages) return [{ type: "separator" }, toggle]
|
||||
|
||||
return [
|
||||
{ type: "separator" },
|
||||
toggle,
|
||||
{
|
||||
label: nativeT("desktop.contextMenu.spellcheckLanguages"),
|
||||
enabled,
|
||||
submenu: nativeLanguageOptions(session.defaultSession.availableSpellCheckerLanguages).map((option) => ({
|
||||
label: option.label,
|
||||
type: "checkbox",
|
||||
checked: current.includes(option.tag),
|
||||
// The last dictionary stays; the Spellcheck checkbox is how to stop checking entirely.
|
||||
enabled: !(current.length === 1 && current[0] === option.tag),
|
||||
click: () => {
|
||||
const next = current.includes(option.tag)
|
||||
? current.filter((language) => language !== option.tag)
|
||||
: [...current, option.tag]
|
||||
|
||||
getStore().set(SPELLCHECK_LANGUAGES_KEY, next)
|
||||
session.defaultSession.setSpellCheckerLanguages(next)
|
||||
},
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Dictionaries picked from the menu win; otherwise follow the system languages on every launch.
|
||||
function spellcheckLanguages() {
|
||||
const available = session.defaultSession.availableSpellCheckerLanguages
|
||||
const stored = getStore().get(SPELLCHECK_LANGUAGES_KEY)
|
||||
const chosen = Array.isArray(stored) ? available.filter((language) => stored.includes(language)) : []
|
||||
|
||||
return chosen.length ? chosen : defaultSpellcheckLanguages(available, app.getPreferredSystemLanguages())
|
||||
}
|
||||
@@ -29,6 +29,16 @@ export function nativeT(key: DesktopNativeKey, params?: Record<string, string |
|
||||
return formatDesktopNativeMessage(bundle.messages[key], params)
|
||||
}
|
||||
|
||||
/** Language tags with their names in the app locale, sorted for a menu. */
|
||||
export function nativeLanguageOptions(tags: readonly string[]) {
|
||||
const locale = DESKTOP_NATIVE_LOCALE_TAGS[bundle.locale]
|
||||
const names = new Intl.DisplayNames([locale], { type: "language" })
|
||||
|
||||
return tags
|
||||
.map((tag) => ({ tag, label: names.of(tag) ?? tag }))
|
||||
.toSorted((a, b) => a.label.localeCompare(b.label, locale))
|
||||
}
|
||||
|
||||
/** The app locale the renderer last pushed. GUI plugin catalogs resolve against it. */
|
||||
export function nativeLocale() {
|
||||
return bundle.locale
|
||||
|
||||
@@ -6,6 +6,10 @@ export const PINCH_ZOOM_ENABLED_KEY = "pinchZoomEnabled"
|
||||
|
||||
export const ZOOM_LEVEL_KEY = "zoomLevel"
|
||||
|
||||
export const SPELLCHECK_ENABLED_KEY = "spellcheckEnabled"
|
||||
|
||||
export const SPELLCHECK_LANGUAGES_KEY = "spellcheckLanguages"
|
||||
|
||||
export const BACKGROUND_COLOR_KEY = "backgroundColor"
|
||||
|
||||
export const WINDOW_IDS_KEY = "windowIds"
|
||||
|
||||
@@ -71,6 +71,8 @@ export type ElectronAPI = {
|
||||
setBackgroundColor(color: string): Promise<void>
|
||||
exportDebugLogs(): Promise<string>
|
||||
setForceFocus(enabled: boolean): Promise<void>
|
||||
getSpellcheckEnabled(): Promise<boolean>
|
||||
setSpellcheckEnabled(enabled: boolean): Promise<void>
|
||||
recordFatalRendererError(error: FatalRendererError): Promise<void>
|
||||
setNativeTranslations(bundle: DesktopNativeBundle): Promise<void>
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ export const api: ElectronAPI = {
|
||||
setBackgroundColor: (color) => invoke("AppSetBackgroundColor", { color }),
|
||||
exportDebugLogs: () => invoke("AppExportDebugLogs"),
|
||||
setForceFocus: (enabled) => invoke("AppSetForceFocus", { enabled }),
|
||||
getSpellcheckEnabled: () => invoke("AppGetSpellcheckEnabled"),
|
||||
setSpellcheckEnabled: (enabled) => invoke("AppSetSpellcheckEnabled", { enabled }),
|
||||
recordFatalRendererError: (error) => invoke("AppRecordFatalRendererError", { error }),
|
||||
setNativeTranslations: (bundle) => invoke("AppSetNativeTranslations", { value: bundle }),
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ export function createDesktopPlatform(api: ElectronAPI, windowState: DesktopWind
|
||||
setTrafficLightsPosition: (position) => api.setTrafficLightsPosition(position),
|
||||
getPinchZoomEnabled: () => api.getPinchZoomEnabled(),
|
||||
setPinchZoomEnabled,
|
||||
getSpellcheckEnabled: () => api.getSpellcheckEnabled(),
|
||||
setSpellcheckEnabled: (enabled) => api.setSpellcheckEnabled(enabled),
|
||||
onDragCancel: (callback) => {
|
||||
window.addEventListener(DragCancelEvent, callback)
|
||||
|
||||
|
||||
@@ -54,6 +54,12 @@ export const AppRecordFatalRendererError = Rpc.make("AppRecordFatalRendererError
|
||||
},
|
||||
})
|
||||
|
||||
export const AppGetSpellcheckEnabled = Rpc.make("AppGetSpellcheckEnabled", { success: Schema.Boolean })
|
||||
|
||||
export const AppSetSpellcheckEnabled = Rpc.make("AppSetSpellcheckEnabled", {
|
||||
payload: { enabled: Schema.Boolean },
|
||||
})
|
||||
|
||||
export const AppSetNativeTranslations = Rpc.make("AppSetNativeTranslations", {
|
||||
payload: { value: Schema.Unknown },
|
||||
})
|
||||
@@ -71,6 +77,8 @@ export const AppRpcs = RpcGroup.make(
|
||||
AppSetBackgroundColor,
|
||||
AppExportDebugLogs,
|
||||
AppSetForceFocus,
|
||||
AppGetSpellcheckEnabled,
|
||||
AppSetSpellcheckEnabled,
|
||||
AppRecordFatalRendererError,
|
||||
AppSetNativeTranslations,
|
||||
AppRelaunch,
|
||||
|
||||
Reference in New Issue
Block a user