Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ This repository provides:
| [`after-hours`](./after-hours) | Auto-replies with a configurable away/closing message to messages received outside business hours. | 0.1.2 | stable |
| [`chat-flow`](./chat-flow) | Interactive, stateful auto-reply: a trigger word starts a greeting + numbered menu, replies traverse a configurable menu tree, and per-chat state expires after 15 minutes. | 1.0.3 | stable |
| [`faq-bot`](./faq-bot) | Auto-replies to inbound WhatsApp messages from configurable FAQ keyword/regex rules. | 0.1.4 | stable |
| [`group-translate`](./group-translate) | Auto-translates group messages between participants' languages via a LibreTranslate backend. Configure in-chat with /tr commands. Admin-gated; disabled until enabled. | 1.0.3 | stable |
| [`group-translate`](./group-translate) | Auto-translates group messages between participants' languages via a LibreTranslate backend. Configure in-chat with /tr commands. Admin-gated; disabled until enabled. | 1.0.4 | stable |
| [`gsheets-logger`](./gsheets-logger) | Logs WhatsApp message events to a Google Sheet via a service account. | 0.2.2 | stable |
| [`voice-transcription`](./voice-transcription) | Transcribes inbound WhatsApp voice notes to text via an OpenAI-compatible speech-to-text backend (self-hosted Speaches/faster-whisper or hosted Groq/OpenAI) and delivers a `message.transcription` event to your webhook — so bots and AI can read and reply to audio. Off the message-delivery path; disabled until enabled. | 1.0.0 | beta |
<!-- END PLUGIN CATALOG -->
Expand Down
9 changes: 9 additions & 0 deletions group-translate/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ The version here always matches `manifest.json`'s `version`.

## [Unreleased]

## [1.0.4] — 2026-06-25

### Fixed

- Translations now actually apply. The LibreTranslate client read the response with `res.json()`, but the
sandboxed `ctx.net.fetch` returns the body as a string and provides no `.json()` method (functions can't
cross the worker boundary) — so every call threw and failed open, a silent no-op. The client now parses
`res.body` directly.

## [1.0.3] — 2026-06-23

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions group-translate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
| Field | Value |
| ----- | ----- |
| **Identifier** | `group-translate` |
| **Version** | 1.0.3 |
| **Released** | 2026-06-23 |
| **Version** | 1.0.4 |
| **Released** | 2026-06-25 |
| **Status** | stable |
| **Author** | Yudhi Armyndharis |
| **License** | MIT |
Expand Down
19 changes: 11 additions & 8 deletions group-translate/libretranslate.client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import assert from 'node:assert/strict';
import type { PluginNetCapability, PluginNetResponse } from '../types/openwa';
import { LibreTranslateClient } from './libretranslate.client.ts';

function res(partial: { ok?: boolean; status?: number; json?: () => Promise<unknown> }): PluginNetResponse {
function res(partial: { ok?: boolean; status?: number; body?: unknown }): PluginNetResponse {
return {
ok: partial.ok ?? true,
status: partial.status ?? 200,
headers: {},
body: '',
// The sandbox runtime returns the response body as a string and provides NO .json() (functions
// can't cross the worker structuredClone boundary). The client must JSON.parse(res.body).
body: partial.body === undefined ? '{}' : JSON.stringify(partial.body),
text: async () => '',
// PluginNetResponse.json is generic (<T>() => Promise<T>); a concrete fake needs the cast.
json: (partial.json ?? (async () => ({}))) as PluginNetResponse['json'],
json: (async () => {
throw new Error('res.json() is not available in the sandbox runtime');
}) as PluginNetResponse['json'],
arrayBuffer: async () => new ArrayBuffer(0),
};
}
Expand All @@ -31,20 +34,20 @@ function fakeNet(handlers: Array<(url: string) => Promise<PluginNetResponse>>) {
}

test('translate posts and returns translatedText on success', async () => {
const { net, calls } = fakeNet([async () => res({ json: async () => ({ translatedText: 'hola' }) })]);
const { net, calls } = fakeNet([async () => res({ body: { translatedText: 'hola' } })]);
const c = new LibreTranslateClient({ url: 'http://lt:7001/', timeoutMs: 4000, net });
assert.equal(await c.translate('hi', 'en', 'es'), 'hola');
assert.equal(calls[0], 'http://lt:7001/translate'); // trailing slash trimmed
});

test('translate throws when the response lacks a translatedText string', async () => {
const { net } = fakeNet([async () => res({ json: async () => ({}) })]); // partial/empty body
const { net } = fakeNet([async () => res({ body: {} })]); // partial/empty body
const c = new LibreTranslateClient({ url: 'http://lt:7001', timeoutMs: 4000, net });
await assert.rejects(c.translate('hi', 'en', 'es'), /translatedText/);
});

test('detect returns the top result', async () => {
const { net } = fakeNet([async () => res({ json: async () => [{ language: 'en', confidence: 0.9 }] })]);
const { net } = fakeNet([async () => res({ body: [{ language: 'en', confidence: 0.9 }] })]);
const c = new LibreTranslateClient({ url: 'http://lt:7001', timeoutMs: 4000, net });
assert.deepEqual(await c.detect('hello'), { lang: 'en', confidence: 0.9 });
});
Expand All @@ -69,7 +72,7 @@ test('opens the circuit after the failure threshold and short-circuits the next

test('a success resets the consecutive-failure counter', async () => {
const fail = async () => { throw new Error('boom'); };
const ok = async () => res({ json: async () => ({ translatedText: 'x' }) });
const ok = async () => res({ body: { translatedText: 'x' } });
const { net } = fakeNet([fail, ok]);
const c = new LibreTranslateClient({ url: 'http://lt:7001', timeoutMs: 4000, net, failureThreshold: 3 });
await assert.rejects(c.translate('a', 'en', 'es'));
Expand Down
4 changes: 3 additions & 1 deletion group-translate/libretranslate.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export class LibreTranslateClient implements Translator {
if (!res.ok) {
throw new Error(`LibreTranslate ${path} -> HTTP ${res.status}`);
}
const data = await res.json();
// The sandbox runtime hands back the body as a string (no res.json() — functions can't cross the
// worker boundary), so parse it here. A malformed/empty body throws and is counted as a failure.
const data = JSON.parse(res.body);
this.consecutiveFailures = 0;
return data;
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion group-translate/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "group-translate",
"name": "Group Auto-Translation",
"version": "1.0.3",
"version": "1.0.4",
"type": "extension",
"main": "dist/index.js",
"description": "Auto-translates group messages between participants' languages via a LibreTranslate backend. Configure in-chat with /tr commands. Admin-gated; disabled until enabled.",
Expand Down
6 changes: 3 additions & 3 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@
{
"id": "group-translate",
"name": "Group Auto-Translation",
"version": "1.0.3",
"version": "1.0.4",
"type": "extension",
"status": "stable",
"description": "Auto-translates group messages between participants' languages via a LibreTranslate backend. Configure in-chat with /tr commands. Admin-gated; disabled until enabled.",
Expand All @@ -556,11 +556,11 @@
],
"minOpenWAVersion": "0.7.0",
"testedOpenWAVersion": "0.7.0",
"releasedAt": "2026-06-23",
"releasedAt": "2026-06-25",
"repoPath": "group-translate",
"repoUrl": "https://github.com/rmyndharis/OpenWA-plugins",
"homepage": "https://github.com/rmyndharis/OpenWA-plugins/tree/main/group-translate",
"download": "https://github.com/rmyndharis/OpenWA-plugins/releases/download/group-translate-v1.0.3/group-translate.zip",
"download": "https://github.com/rmyndharis/OpenWA-plugins/releases/download/group-translate-v1.0.4/group-translate.zip",
"i18n": {
"es": {
"name": "Traducción Automática de Grupos",
Expand Down
Loading