Skip to content

Commit

Permalink
Merge pull request #2391 from opral/introduce-machine-translate-vscode
Browse files Browse the repository at this point in the history
  • Loading branch information
felixhaeberle authored Mar 21, 2024
2 parents 5ef7906 + 35138c0 commit 79ae51a
Show file tree
Hide file tree
Showing 16 changed files with 243 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-dogs-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vs-code-extension": minor
---

add machine translate
1 change: 1 addition & 0 deletions inlang/source-code/ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
},
"dependencies": {
"@inlang/result": "workspace:*",
"@inlang/rpc": "workspace:*",
"@inlang/sdk": "workspace:*",
"@inlang/telemetry": "workspace:*",
"@lix-js/client": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion inlang/source-code/ide-extension/src/commands/copyError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { msg } from "../utilities/messages/msg.js"

export const copyErrorCommand = {
command: "sherlock.copyError",
title: "Inlang: Copy error",
title: "Sherlock: Copy error",
register: vscode.commands.registerCommand,
callback: async (error: ErrorNode) => {
vscode.env.clipboard.writeText(`${error.label}: ${error.tooltip}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { CONFIGURATION } from "../configuration.js"

export const editMessageCommand = {
command: "sherlock.editMessage",
title: "Inlang: Edit a Message",
title: "Sherlock: Edit a Message",
register: commands.registerCommand,
callback: async function ({
messageId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { isQuoted, stripQuotes } from "../utilities/messages/isQuoted.js"
*/
export const extractMessageCommand = {
command: "sherlock.extractMessage",
title: "Inlang: Extract Message",
title: "Sherlock: Extract Message",
register: commands.registerTextEditorCommand,
callback: async function (textEditor: TextEditor) {
const ideExtension = state().project.customApi()["app.inlang.ideExtension"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as vscode from "vscode"

export const jumpToPositionCommand = {
command: "sherlock.jumpToPosition",
title: "Inlang: Jump to position in editor",
title: "Sherlock: Jump to position in editor",
register: commands.registerCommand,
callback: async function (args: {
messageId: Message["id"]
Expand Down
147 changes: 147 additions & 0 deletions inlang/source-code/ide-extension/src/commands/machineTranslate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { rpc } from "@inlang/rpc"
import { CONFIGURATION } from "../configuration.js"
import { machineTranslateMessageCommand } from "./machineTranslate.js"
import { msg } from "../utilities/messages/msg.js"

vi.mock("vscode", () => ({
commands: {
registerCommand: vi.fn(),
},
StatusBarAlignment: {
Left: 1,
Right: 2,
},
window: {
createStatusBarItem: vi.fn(() => ({
show: vi.fn(),
text: "",
})),
},
}))

vi.mock("@inlang/rpc", () => ({
rpc: {
machineTranslateMessage: vi.fn(),
},
}))

vi.mock("../configuration", () => ({
CONFIGURATION: {
EVENTS: {
ON_DID_EDIT_MESSAGE: {
fire: vi.fn(),
},
},
},
}))

vi.mock("../utilities/messages/msg", () => ({
msg: vi.fn(),
}))

vi.mock("../utilities/state", () => ({
state: () => ({
project: {
query: {
messages: {
get: (args: any) => {
if (args.where && args.where.id === "validId") {
return mockMessage
}
return undefined
},
upsert: vi.fn(),
},
},
},
}),
}))

const mockMessage = {
id: "validId",
alias: {},
selectors: [],
variants: [
{
languageTag: "en",
match: [],
pattern: [
{
type: "Text",
value: "Original content",
},
],
},
],
}

describe("machineTranslateMessageCommand", () => {
beforeEach(() => {
vi.clearAllMocks()
})

it("should return a message if messageId is not found", async () => {
await machineTranslateMessageCommand.callback({
messageId: "nonexistent",
sourceLanguageTag: "en",
targetLanguageTags: ["es"],
})

expect(msg).toHaveBeenCalledWith("Message with id nonexistent not found.")
})

it("should return an error message on RPC error", async () => {
// @ts-expect-error
rpc.machineTranslateMessage.mockResolvedValueOnce({ error: "RPC Error" })

await machineTranslateMessageCommand.callback({
messageId: "validId",
sourceLanguageTag: "en",
targetLanguageTags: ["es"],
})

expect(msg).toHaveBeenCalledWith("Error translating message: RPC Error")
})

it("should return a message if no translation is available", async () => {
// @ts-expect-error
rpc.machineTranslateMessage.mockResolvedValueOnce({ data: undefined })

await machineTranslateMessageCommand.callback({
messageId: "validId",
sourceLanguageTag: "en",
targetLanguageTags: ["es"],
})

expect(msg).toHaveBeenCalledWith("No translation available.")
})

it("should successfully translate and update a message", async () => {
const mockTranslation = { translatedText: "Translated content" }
// @ts-expect-error
rpc.machineTranslateMessage.mockResolvedValueOnce({ data: mockTranslation })

await machineTranslateMessageCommand.callback({
messageId: "validId",
sourceLanguageTag: "en",
targetLanguageTags: ["es"],
})

expect(msg).toHaveBeenCalledWith("Message translated.")
})

it("should emit ON_DID_EDIT_MESSAGE event after successful translation", async () => {
const mockTranslation = { translatedText: "Translated content" }
// @ts-expect-error
rpc.machineTranslateMessage.mockResolvedValueOnce({ data: mockTranslation })

await machineTranslateMessageCommand.callback({
messageId: "validId",
sourceLanguageTag: "en",
targetLanguageTags: ["es"],
})

expect(CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.fire).toHaveBeenCalled()
})
})
55 changes: 55 additions & 0 deletions inlang/source-code/ide-extension/src/commands/machineTranslate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { commands } from "vscode"
import { LanguageTag, Message } from "@inlang/sdk"
import { state } from "../utilities/state.js"
import { msg } from "../utilities/messages/msg.js"
import { rpc } from "@inlang/rpc"
import { CONFIGURATION } from "../configuration.js"

export const machineTranslateMessageCommand = {
command: "sherlock.machineTranslateMessage",
title: "Sherlock: Machine Translate Message",
register: commands.registerCommand,
callback: async function ({
messageId,
sourceLanguageTag,
targetLanguageTags,
}: {
messageId: Message["id"]
sourceLanguageTag: LanguageTag
targetLanguageTags: LanguageTag[]
}) {
// Get the message from the state
const message = state().project.query.messages.get({ where: { id: messageId } })
if (!message) {
return msg(`Message with id ${messageId} not found.`)
}

// Call machine translation RPC function
const result = await rpc.machineTranslateMessage({
message,
sourceLanguageTag,
targetLanguageTags,
})

if (result.error) {
return msg(`Error translating message: ${result.error}`)
}

// Update the message with the translated content
const updatedMessage = result.data
if (!updatedMessage) {
return msg("No translation available.")
}

state().project.query.messages.upsert({
where: { id: messageId },
data: updatedMessage,
})

// Emit event to notify that a message was edited
CONFIGURATION.EVENTS.ON_DID_EDIT_MESSAGE.fire()

// Return success message
return msg("Message translated.")
},
} as const
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getGitOrigin } from "../utilities/settings/getGitOrigin.js"

export const openInEditorCommand = {
command: "sherlock.openInEditor",
title: "Inlang: Open in Editor",
title: "Sherlock: Open in Editor",
register: commands.registerCommand,
callback: async function (args: { messageId: Message["id"]; selectedProjectPath: string }) {
// TODO: Probably the origin should be configurable via the config.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { NodeishFilesystem } from "@lix-js/fs"

export const openProjectCommand = {
command: "sherlock.openProject",
title: "Inlang: Open project",
title: "Sherlock: Open project",
register: vscode.commands.registerCommand,
callback: async (
node: ProjectViewNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as path from "node:path"

export const openSettingsFileCommand = {
command: "sherlock.openSettingsFile",
title: "Inlang: Open settings file",
title: "Sherlock: Open settings file",
register: vscode.commands.registerCommand,
callback: async (node: ProjectViewNode) => {
const settingsFile = vscode.Uri.file(path.join(node.path, "settings.json"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("previewLanguageTagCommand", () => {

it("should register the command", () => {
expect(previewLanguageTagCommand.command).toBe("sherlock.previewLanguageTag")
expect(previewLanguageTagCommand.title).toBe("Inlang: Change preview language tag")
expect(previewLanguageTagCommand.title).toBe("Sherlock: Change preview language tag")
expect(previewLanguageTagCommand.register).toBe(vscode.commands.registerCommand)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CONFIGURATION } from "../configuration.js"

export const previewLanguageTagCommand = {
command: "sherlock.previewLanguageTag",
title: "Inlang: Change preview language tag",
title: "Sherlock: Change preview language tag",
register: vscode.commands.registerCommand,
callback: async () => {
const settings = state().project?.settings()
Expand Down
2 changes: 2 additions & 0 deletions inlang/source-code/ide-extension/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { ErrorNode } from "./utilities/errors/errors.js"
import { copyErrorCommand } from "./commands/copyError.js"
import { previewLanguageTagCommand } from "./commands/previewLanguageTagCommand.js"
import { jumpToPositionCommand } from "./commands/jumpToPosition.js"
import { machineTranslateMessageCommand } from "./commands/machineTranslate.js"

export const CONFIGURATION = {
EVENTS: {
Expand All @@ -27,6 +28,7 @@ export const CONFIGURATION = {
OPEN_PROJECT: openProjectCommand,
OPEN_SETTINGS_FILE: openSettingsFileCommand,
COPY_ERROR: copyErrorCommand,
MACHINE_TRANSLATE_MESSAGE: machineTranslateMessageCommand,
},
FILES: {
// TODO: remove this hardcoded assumption for multi project support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ export function getTranslationsTableHtml(args: {
}

const editCommand = `editMessage('${args.message.id}', '${escapeHtml(languageTag)}')`
const machineTranslateCommand = `machineTranslate('${args.message.id}', '${
state().project.settings()?.sourceLanguageTag
}', ['${languageTag}'])`

return `
<div class="section">
Expand All @@ -302,7 +305,16 @@ export function getTranslationsTableHtml(args: {
m
)}</button></span>
<span class="actionButtons">
<button title="Edit" onclick="${editCommand}"><span class="codicon codicon-edit"></span></button>
${
m === CONFIGURATION.STRINGS.MISSING_TRANSLATION_MESSAGE
? `
<button title="Machine translate" onclick="${machineTranslateCommand}">
<svg slot="prefix" height="16px" width="16px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m15.075 18.95l-.85 2.425q-.1.275-.35.45t-.55.175q-.5 0-.812-.413t-.113-.912l3.8-10.05q.125-.275.375-.45t.55-.175h.75q.3 0 .55.175t.375.45L22.6 20.7q.2.475-.1.888t-.8.412q-.325 0-.562-.175t-.363-.475l-.85-2.4zM9.05 13.975L4.7 18.3q-.275.275-.687.288T3.3 18.3q-.275-.275-.275-.7t.275-.7l4.35-4.35q-.875-.875-1.588-2T4.75 8h2.1q.5.975 1 1.7t1.2 1.45q.825-.825 1.713-2.313T12.1 6H2q-.425 0-.712-.288T1 5q0-.425.288-.712T2 4h6V3q0-.425.288-.712T9 2q.425 0 .713.288T10 3v1h6q.425 0 .713.288T17 5q0 .425-.288.713T16 6h-1.9q-.525 1.8-1.575 3.7t-2.075 2.9l2.4 2.45l-.75 2.05zM15.7 17.2h3.6l-1.8-5.1z" fill="currentColor"/>
</svg>
</button>`
: `<button title="Edit" onclick="${editCommand}"><span class="codicon codicon-edit"></span></button>`
}
</span>
</div>
`
Expand Down Expand Up @@ -451,6 +463,14 @@ export function getHtml(args: {
commandArgs: { messageId, position: decodedPosition },
});
}
function machineTranslate(messageId, sourceLanguageTag, targetLanguageTags) {
vscode.postMessage({
command: 'executeCommand',
commandName: 'sherlock.machineTranslateMessage',
commandArgs: { messageId, sourceLanguageTag, targetLanguageTags },
});
}
</script>
</body>
</html>
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 79ae51a

Please sign in to comment.