- SignalDesk2小时前
Original Summary
去年,我在论坛看到了MTranServer这个项目。核心理念就是把FireFox机器翻译模型下载下来本地推理,整合成一个本地部署的DeepLX API。但是,当时的体验不是特别好,导致我用了几天就把它删除了。最近几个月,自从龙虾出来了以后,各种Agent对于Token的使用量越来越多了,能够白嫖的API的渠道也是越来越少,而且还特别不稳定,比如最近新出来的那个哔哩哔哩Index翻译接口也是经常412风控。思来想去还是决定使用Firefox这个机器翻译,因为它对资源的占用很小,并且速度也很快,虽然翻译质量上有点欠缺,但是对于普通网页的翻译请求也差不多够了。 今天,我搜到了 LinguaSpark 这个项目,代码完全开源,并且可以直接用Docker Compose部署,模型自己下载Docker挂载,先把docker compose文件分享一下。 — compose.yml services: translation-service: image: ghcr.io/linguaspark/server:main ports: - "3000:3000" volumes: - ./models:/app/models # 关键:挂载模型目录 environment: API_KEY: "" # 可选,留空则不启用密钥保护 restart: unless-stopped healthcheck: test: ["CMD", "/bin/sh", "-c", "echo -e 'GET /health HTTP/1.1\r\nHost: localhost:3000\r\n\r\n' | timeout 5 bash -c 'cat > /dev/tcp/localhost/3000' && echo 'Health check passed'"] interval: 30s timeout: 10s retries: 3 然后就是准备模型文件,你在 compose.yml 同路径创建两个文件夹,一个是 en-zh ,另外一个是 zh-en ,前者是放英译中模型的,后者是中译英模型的。 接下来就是要找模型文件的下载链接,通过大量的搜索,终于找到了, https://searchfox.org/firefox-release/source/services/settings/dumps/main/translations-models.json ,结果发现它是Json文件,看的人头大,链接里面的模型众多,不同版本鱼龙混杂,于是就搓了一个油猴解析脚本,贴在下面了。 // ==UserScript== // @name Firefox 模型按语言对+版本归类下载 // @namespace https://searchfox.org/firefox-release/source/services/settings/dumps/main/translations-models.json // @version 1.0 // @description 从 translations-models.json 解析,按语言对分组、组内按版本归类,默认只显示最新版本,下载自动重命名 // @match https://searchfox.org/firefox-release/source/services/settings/dumps/main/translations-models.json // @grant GM_download // ==/UserScript== (function () { 'use strict'; const CDN = 'https://firefox-settings-attachments.cdn.mozilla.net/'; // ---------- 1. 提取 JSON ---------- function extractModels() { const text = document.body.innerText; const dataIdx = text.indexOf('"data"'); if (dataIdx === -1) return null; const jsonStart = text.lastIndexOf('{', dataIdx); const jsonEnd = text.lastIndexOf('}'); if (jsonStart === -1 || jsonEnd === -1 || jsonEnd <= jsonStart) return null; try { const obj = JSON.parse(text.slice(jsonStart, jsonEnd + 1)); return Array.isArray(obj.data) ? obj.data : null; } catch (e) { console.warn('[Firefox Models] JSON 解析失败', e); return null; } } // ---------- 2. 版本号比较:2.2 > 2.1 > 2.0 > 2.0a2 ---------- function compareVersion(a, b) { const parse = v => String(v).split(/[.\-]/).flatMap(part => { const m = part.match(/^(\d+)([a-z])(\d)$/i); if (!m) return [part]; const arr = [Number(m[1])]; if (m[2]) arr.push(m[2]); if (m[3]) arr.push(Number(m[3])); return arr; }); const pa = parse(a), pb = parse(b); const len = Math.max(pa.length, pb.length); for (let i = 0; i < len; i++) { const x = pa[i], y = pb[i]; if (x === undefined) return -1; if (y === undefined) return 1; if (typeof x === 'number' && typeof y === 'number') { if (x !== y) return x - y; } else { const xs = String(x), ys = String(y); if (xs !== ys) return xs > ys ? 1 : -1; } } return 0; } // ---------- 3. 归类:语言对 -> 版本 -> 文件类型 -> 条目 ---------- function groupModels(models) { const groups = {}; models.forEach(m => { const pair =
${m.fromLang}-${m.toLang}; const ver = m.version || 'unknown'; const type = m.fileType || 'unknown'; groups[pair] ??= {}; groups[pair][ver] ??= {}; groups[pair][ver][type] ??= []; groups[pair][ver][type].push(m); }); return groups; } function sortedVersions(versionMap) { return Object.keys(versionMap).sort((a, b) => compareVersion(b, a)); } function isComplete(types) { return !!types.model?.length && !!types.lex?.length && !!(types.vocab?.length || types.srcvocab?.length || types.trgvocab?.length); } function urlOf(m) { return CDN + m.attachment.location; } // ---------- 4. 下载(自动重命名) ---------- function downloadOne(m) { const url = urlOf(m); const filename = m.attachment.filename || m.name; GM_download({ url: url, name: filename, saveAs: false, onerror: err => console.error('[Firefox Models] 下载失败:', filename, err) }); } function downloadAll(entries) { entries.forEach((m, i) => setTimeout(() => downloadOne(m), i * 400)); } // ---------- 5. UI ---------- function buildPanel(groups) { const box = document.createElement('div'); box.style.cssText =position:fixed;top:10px;right:10px;z-index:99999; background:#1a1a1a;color:#ddd;padding:14px;border-radius:10px; font:12px/1.6 monospace;max-height:80vh;overflow:auto;width:500px; box-shadow:0 6px 30px rgba(0,0,0,.6);border:1px solid #333;; const title = document.createElement('div'); title.innerHTML =<b >Firefox 模型归类</b> <span >${Object.keys(groups).length} 个语言对</span>; title.style.cssText = 'margin-bottom:10px;font-size:13px;'; box.appendChild(title); const search = document.createElement('input'); search.placeholder = '筛选语言对,如 en-zh'; search.style.cssText = 'width:100%;margin-bottom:10px;background:#2a2a2a;color:#ddd;border:1px solid #444;padding:5px;border-radius:4px;box-sizing:border-box;'; box.appendChild(search); const list = document.createElement('div'); box.appendChild(list); function render(filter = '') { list.innerHTML = ''; const pairs = Object.keys(groups).filter(k => k.includes(filter)).sort(); if (!pairs.length) { list.innerHTML = '<div >没有匹配的语言对</div>'; return; } pairs.forEach(pair => { const versionMap = groups[pair]; const versions = sortedVersions(versionMap); const latest = versions[0]; const card = document.createElement('div'); card.style.cssText = 'margin-bottom:12px;padding:8px;background:#222;border-radius:6px;border-left:3px solid #6cf;'; const head = document.createElement('div'); head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;'; head.innerHTML =<span >${pair}</span> <span >${versions.length} 个版本</span>; card.appendChild(head); function renderVersion(ver, isLatest) { const types = versionMap[ver]; const complete = isComplete(types); const entries = Object.values(types).flat(); const vBox = document.createElement('div'); vBox.style.cssText = `margin:6px 0;padding:6px;background:${is- 情报分类:技术学习与提效
- 分类依据:内容涉及技术、AI、软件工具或工程实践
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/10/8 23:39:56
- 暂无回复