Translate rclone RC responses on the fly
Rewrites translatable strings in config/providers and options/info responses · Pairs with rclone-sdk · Works with Vanilla Fetch
npm install rclone-i18nThe package ships a single translate function. Pass it either a raw Response or a parsed JSON body, and it returns the same shape with all known translatable fields rewritten into the target language.
Two modes, pick whichever suits you.
import { translate } from 'rclone-i18n'
const translated = await translate(body, { lang: 'fr' })The language pack is fetched once from jsDelivr with cache: 'force-cache', so subsequent calls are served from the HTTP cache. Supported codes: fr, ja, zh, es.
import { translate } from 'rclone-i18n'
const fr = await fetch('/locales/fr.json').then((r) => r.json())
const translated = await translate(body, { messages: fr })Useful when you want to bundle the language pack, ship it from your own CDN, or override individual strings. Grab the source files from languages/ in this repo.
Pass the raw Response and get a Response back, with the same status, statusText, and headers:
import { translate } from 'rclone-i18n'
const res = await fetch('http://localhost:5572/config/providers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}',
})
const translatedRes = await translate(res, { lang: 'fr' })
const { providers } = await translatedRes.json()
console.log(providers.find((p) => p.Name === 'local').Description)
// → "Disque local"Or pass the parsed body directly:
const body = await res.json()
const translated = await translate(body, { lang: 'fr' })The original input is never mutated — bodies are deep-cloned, and a fresh Response is returned.
rclone-sdk returns { data, error, response } from each call. Translate data directly:
import createRCDClient from 'rclone-sdk'
import { translate } from 'rclone-i18n'
const rcd = createRCDClient({ baseUrl: 'http://localhost:5572' })
const { data, error } = await rcd.POST('/config/providers')
if (error || !data) throw error
const translated = await translate(data, { lang: 'fr' })
for (const provider of translated.providers ?? []) {
console.log(provider.Name, '→', provider.Description)
}Same thing with /options/info:
const { data } = await rcd.POST('/options/info')
const translated = await translate(data, { lang: 'fr' })
const filter = translated.filter
const maxSize = filter?.find((o) => o.Name === 'max_size')
console.log(maxSize?.Help)
// → "Ne transférer que les fichiers plus petits que cette valeur, …"translate walks the response and rewrites these fields when a matching entry exists in the language pack:
/config/providersproviders[].Descriptionproviders[].Options[].Help(withProvideroverride)providers[].Options[].Examples[].Help(incl. emptyValue: "")providers[].CommandHelp[].Shortand.Longproviders[].MetadataInfo.HelpandMetadataInfo.System[k].Help
/options/info<block>[].Help(e.g.filter,main,vfs, …)<block>[].Examples[].Help
Any other field is passed through unchanged. The function also returns the input untouched when:
- the input is a
Responsewith!okstatus, or non-JSONContent-Type - the input is a parsed body shaped like an rclone error (
{ error: string, … })
Language packs live in languages/. The English source-of-truth (en.json) is regenerated from a live rclone daemon:
# rclone rcd --rc-no-auth --rc-addr 127.0.0.1:5572 &
npm run extract:i18nscripts/check-i18n.ts (run via npm run check:i18n) compares the live extraction against languages/en.json and fails CI if keys drift. Two manual workflows wrap it: check-release runs against the latest released rclone, check-build builds rclone from master.
To add a new translation, copy languages/en.json, translate the leaf strings, and open a PR. Keys must match exactly — __empty__ is the sentinel for examples whose Value is "".
Contributions = welcome!