diff --git a/addons/face/ui/src/ctx.jsx b/addons/face/ui/src/ctx.jsx
index 92c318f7ff..0a21f7ef57 100644
--- a/addons/face/ui/src/ctx.jsx
+++ b/addons/face/ui/src/ctx.jsx
@@ -1,4 +1,4 @@
-import en from './i18n/en.js'
+import { locales } from './i18n/index.js'
import { modelFrom as pickModel } from './lib/model.js'
// The one object every page factory of the Face add-on receives. It binds the
@@ -8,12 +8,25 @@ export function createContext(sdk) {
const { React } = sdk
const h = React.createElement
const Fragment = React.Fragment
- sdk.locale.define('en', Object.fromEntries(Object.entries(en).filter(([, v]) => typeof v === 'string')))
- const lists = Object.fromEntries(Object.entries(en).filter(([, v]) => Array.isArray(v)))
+ // Every language of the page; a key a language lacks reads in English.
+ for (const [language, bundle] of Object.entries(locales)) sdk.locale.define(language, bundle)
+ const en = locales.en
const t = (key, vars) => sdk.locale.t(key, key, vars)
- // A list of strings or objects (the "why" and "stored" notes).
- const list = (key) => lists[key] || []
+ // A list of strings or objects (the "why" and "stored" notes). The bundles hold
+ // each item under a numbered key (key.0, or key.0.field for an object), so
+ // that every item is translated like any other text.
+ const list = (key) => {
+ const out = []
+ for (let i = 0; ; i++) {
+ const own = `${key}.${i}`
+ if (typeof en[own] === 'string') { out.push(t(own)); continue }
+ const prefix = `${own}.`
+ const fields = Object.keys(en).filter((k) => k.startsWith(prefix))
+ if (fields.length === 0) return out
+ out.push(Object.fromEntries(fields.map((k) => [k.slice(prefix.length), t(k)])))
+ }
+ }
// A link to a page of this add-on, as an anchor the shell navigates.
function Link({ page, query, className, children, ...rest }) {
diff --git a/addons/face/ui/src/i18n/face.de.json b/addons/face/ui/src/i18n/face.de.json
new file mode 100644
index 0000000000..16044fc6c0
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.de.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Gesichter",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "In Arbeit",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "Sucht",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Entfernen",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Abbrechen",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Stopp",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Vorschau",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Name",
+ "known.added": "Hinzugefügt",
+ "known.actions": "Aktionen",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Rückgängig",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "{{name}} entfernen",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "Schließen",
+ "enrol.cancel": "Abbrechen",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Name",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Erneut versuchen",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installieren",
+ "needed.installing": "Wird installiert",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/en.js b/addons/face/ui/src/i18n/face.en.json
similarity index 91%
rename from addons/face/ui/src/i18n/en.js
rename to addons/face/ui/src/i18n/face.en.json
index efa468ebb7..3db65a6b48 100644
--- a/addons/face/ui/src/i18n/en.js
+++ b/addons/face/ui/src/i18n/face.en.json
@@ -1,5 +1,4 @@
-// Generated once from the host locale files; edit by hand from here on.
-export default {
+{
"faces.title": "Faces",
"faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
"tool.aria": "Recognition tool",
@@ -52,25 +51,15 @@ export default {
"face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
"face.moreTitle": "More tools",
"face.moreHint": "detect faces, guess traits, get the raw faceprint",
- "face.why": [
- "The server matches one face per photo. A group photo is matched on a single face.",
- "Light, angle, glasses and blur change a face a lot.",
- "This is a similarity score, not a security check. Do not use it to unlock anything."
- ],
- "face.stored": [
- {
- "where": "On the server",
- "what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept."
- },
- {
- "where": "In this browser",
- "what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling."
- },
- {
- "where": "Photos you test with",
- "what": "They go to the model on this server to be compared and are not kept."
- }
- ],
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
"same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
"same.distance": "distance",
"word.strong": "Strong match",
diff --git a/addons/face/ui/src/i18n/face.es.json b/addons/face/ui/src/i18n/face.es.json
new file mode 100644
index 0000000000..3c25829f76
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.es.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Rostros",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "En marcha",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "Buscando",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Quitar",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Cancelar",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Detener",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Vista previa",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nombre",
+ "known.added": "Añadido",
+ "known.actions": "Acciones",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Deshacer",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Quitar {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "Cerrar",
+ "enrol.cancel": "Cancelar",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nombre",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Reintentar",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Instalar",
+ "needed.installing": "Instalando",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/face.fr.json b/addons/face/ui/src/i18n/face.fr.json
new file mode 100644
index 0000000000..157840b265
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.fr.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Visages",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "En cours",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "Recherche",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Enregistrer",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Retirer",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Annuler",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Arrêter",
+ "clip.start": "Démarrer l'enregistrement",
+ "clip.recorded": "Enregistrement",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Aperçu",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nom",
+ "known.added": "Ajouté",
+ "known.actions": "Actions",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Annuler",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Retirer {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "Fermer",
+ "enrol.cancel": "Annuler",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Enregistrement",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Enregistrement",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nom",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Transcription exacte",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Réessayer",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installer",
+ "needed.installing": "Installation en cours",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Parcourir tous les modèles",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/face.id.json b/addons/face/ui/src/i18n/face.id.json
new file mode 100644
index 0000000000..7f0e292cfc
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.id.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Wajah",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "Sedang dikerjakan",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "Mencari",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Rekam",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Hapus",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Batal",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Berhenti",
+ "clip.start": "Mulai merekam",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Pratinjau",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nama",
+ "known.added": "Ditambahkan",
+ "known.actions": "Aksi",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Urungkan",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Hapus {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "Tutup",
+ "enrol.cancel": "Batal",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nama",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Coba lagi",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Pasang",
+ "needed.installing": "Memasang",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/face.it.json b/addons/face/ui/src/i18n/face.it.json
new file mode 100644
index 0000000000..f3b6010053
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.it.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Volti",
+ "faces.lede": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona. Le foto vanno solo al modello che gira su questo server.",
+ "tool.aria": "Strumento di riconoscimento",
+ "tool.sameTab": "Stessa persona?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Foto",
+ "tool.photoA": "Foto A",
+ "tool.photoB": "Foto B",
+ "tool.compare": "Confronta",
+ "tool.comparing": "Confronto in corso",
+ "tool.workingAria": "In corso",
+ "face.person": "persona",
+ "face.print": "impronta del volto",
+ "face.whoTab": "Chi è",
+ "face.whoLede": "Confronta una foto con le persone che hai registrato.",
+ "face.sameLede": "Due foto, una domanda. Non serve aver registrato nessuno.",
+ "face.whoGo": "Scopri chi è",
+ "face.working": "Cerca",
+ "face.whoNote": "Cerca tra le persone registrate su questo server. La foto che usi per il test non viene conservata.",
+ "face.sameNote": "La risposta viene da una sola impronta del volto per foto. Non viene conservato nulla.",
+ "face.workingText": "Confronto della foto con le impronte del volto che il server conserva.",
+ "face.probably": "Probabilmente {{name}}",
+ "face.matchText": "L'impronta del volto di {{name}} è la più vicina tra {{count}} confrontate, a distanza {{distance}}. Sotto {{cutoff}} si considera la stessa persona.",
+ "face.nobody": "Nessuno dei registrati assomiglia a questo volto",
+ "face.missText": "La più vicina era {{name}} a distanza {{distance}}, oltre la soglia di {{cutoff}}. Di solito significa una persona nuova, oppure una foto sfocata, di profilo o con poca luce.",
+ "face.noneEnrolled": "Non c'è nessuno registrato con cui confrontare",
+ "face.noneEnrolledText": "Registra prima una persona, poi cerca una foto rispetto a lei.",
+ "face.noneReturned": "Il server non ha restituito nessuna persona",
+ "face.noneReturnedText": "Non conserva impronte del volto create con questo modello. Se il server è stato riavviato, i volti registrati sono persi: registrali di nuovo.",
+ "face.enrolThis": "Registra questo volto",
+ "face.same": "Probabilmente la stessa persona",
+ "face.different": "Probabilmente persone diverse",
+ "face.knownTitle": "Persone note",
+ "face.enrol": "Registra una persona",
+ "face.enrolTitle": "Registra una persona",
+ "face.enrolIntro": "Il server conserva un'impronta del volto e un nome. Non conserva la foto.",
+ "face.emptyTitle": "Nessuno è ancora registrato in questo browser",
+ "face.emptyText": "Registra una persona con una foto nitida, poi prova una foto rispetto a lei.",
+ "face.agreedHint": "Ho il suo permesso, o un'altra base legittima, per conservare la sua impronta del volto.",
+ "face.keepHint": "Ti permette di vedere la foto e di registrare di nuovo dopo il riavvio del server. Disattivato per impostazione predefinita. Resta solo in questo browser.",
+ "face.antiSpoof": "Controlla le foto di foto",
+ "face.antiSpoofHint": "Serve un modello che lo supporti.",
+ "face.real": "Reale",
+ "face.spoof": "Falsa",
+ "face.found": "Volto nella foto {{n}}",
+ "face.neededTitle": "I volti richiedono un modello per i volti",
+ "face.neededText": "Il riconoscimento facciale trasforma una foto in un'impronta del volto e confronta le impronte. Funziona con un modello di riconoscimento facciale, e su questo server non ne è installato nessuno.",
+ "face.neededWhat": "Installa un modello che supporti il riconoscimento facciale. Appena ne è installato uno, questa pagina si attiva da sola. Gli utenti hanno bisogno anche del permesso di riconoscimento facciale.",
+ "face.moreTitle": "Altri strumenti",
+ "face.moreHint": "rileva volti, stima caratteristiche, ottieni l'impronta del volto grezza",
+ "face.why.0": "Il server confronta un solo volto per foto. In una foto di gruppo viene confrontato un solo volto.",
+ "face.why.1": "Luce, angolazione, occhiali e sfocatura cambiano molto un volto.",
+ "face.why.2": "È un punteggio di somiglianza, non un controllo di sicurezza. Non usarlo per sbloccare nulla.",
+ "face.stored.0.where": "Sul server",
+ "face.stored.0.what": "Un'{{print}} (numeri che descrivono il volto, non l'immagine), un nome e le tue etichette. Restano in memoria e si perdono al riavvio del server. La foto non viene conservata.",
+ "face.stored.1.where": "In questo browser",
+ "face.stored.1.what": "Nome, etichette e ora di ogni persona che hai registrato. Una copia della foto solo se spunti Conserva una copia durante la registrazione.",
+ "face.stored.2.where": "Foto che usi per il test",
+ "face.stored.2.what": "Vanno al modello su questo server per essere confrontate e non vengono conservate.",
+ "same.text": "Distanza tra le due impronte ({{print}}): {{distance}}. Sotto {{cutoff}} si considera la stessa persona.",
+ "same.distance": "distanza",
+ "word.strong": "Corrispondenza forte",
+ "word.likely": "Corrispondenza probabile",
+ "word.close": "Caso al limite",
+ "word.near": "Appena oltre la soglia",
+ "scale.aria": "Scala delle distanze con la soglia a {{cutoff}}. Più basso significa più simile. {{dots}}",
+ "scale.cutoff": "soglia {{value}}",
+ "scale.axis": "distanza tra impronte ({{print}}), più bassa significa più simili",
+ "scale.oneDot": "un punto per ogni registrazione ({{person}})",
+ "scale.onePair": "un punto per la coppia",
+ "ranks.aria": "Persone più vicine, dalla più vicina",
+ "ranks.within": "entro la soglia",
+ "ranks.far": "troppo lontana",
+ "cutoff.label": "Soglia",
+ "cutoff.default": "predefinita del modello",
+ "cutoff.reset": "Ripristina a {{value}}",
+ "cutoff.stricter": "Più severa",
+ "cutoff.looser": "Più larga",
+ "cutoff.explain": "Più severa significa meno corrispondenze sbagliate e più mancate. La risposta qui sopra si aggiorna mentre la sposti. Il server non viene interpellato di nuovo.",
+ "how.title": "Come leggere questo risultato.",
+ "how.body": "La distanza dice quanto sono lontane due impronte ({{print}}); 0 significa identiche. La soglia è la linea tra stessa persona e persone diverse. La parola Forte o Probabile dice quanto un risultato sta dentro la linea. Non è una probabilità.",
+ "why.title": "Perché potrebbe essere sbagliato",
+ "why.short": "La clip è breve: {{s}} s. Funziona meglio con almeno tre secondi di voce.",
+ "clip.mic": "microfono",
+ "clip.camera": "fotocamera",
+ "clip.record": "Registra",
+ "clip.takePhoto": "Scatta una foto",
+ "clip.chooseAudio": "Scegli un file",
+ "clip.choosePhoto": "Scegli una foto",
+ "clip.readAloud": "Leggi a voce alta, a ritmo normale",
+ "clip.remove": "Rimuovi",
+ "clip.blocked": "L'accesso a «{{thing}}» è bloccato. Consentilo nelle impostazioni del sito del browser e riprova, oppure scegli un file.",
+ "clip.insecure": "Il browser apre «{{thing}}» solo su https o su localhost. Ora sei su {{origin}}. Apri questa pagina da localhost o aggiungi TLS, oppure scegli un file.",
+ "clip.unsupported": "Questo browser non può aprire «{{thing}}». Scegli invece un file.",
+ "clip.missing": "Su questo dispositivo non è stato trovato «{{thing}}». Scegli invece un file.",
+ "clip.failed": "Non è stato possibile aprire «{{thing}}». Scegli invece un file.",
+ "clip.allowed": "Microfono consentito.",
+ "clip.capture": "Acquisisci",
+ "clip.cancel": "Annulla",
+ "clip.recording": "Registrazione in corso, {{s}} s",
+ "clip.micReady": "Microfono pronto",
+ "clip.stop": "Ferma",
+ "clip.start": "Avvia la registrazione",
+ "clip.recorded": "Registrazione",
+ "clip.photographed": "Foto",
+ "clip.uploaded": "File caricato",
+ "clip.goodLength": "durata adatta",
+ "clip.short": "breve",
+ "clip.checkLevel": "controlla il livello",
+ "clip.preview": "Anteprima",
+ "clip.photoAlt": "La foto che hai scelto",
+ "known.caption": "Persone salvate in questo browser, con lo stato della loro {{noun}}",
+ "known.name": "Nome",
+ "known.added": "Aggiunto",
+ "known.actions": "Azioni",
+ "known.best": "corrispondenza migliore",
+ "known.notOnServer": "non sul server",
+ "known.notInBrowser": "non nell'elenco di questo browser",
+ "known.removing": "rimozione",
+ "known.goneText": "L'ultima ricerca non ha restituito questa persona. Il server potrebbe essere stato riavviato, quindi l'{{print}} è persa, oppure è stata registrata con un altro modello.",
+ "known.extraText": "Il server ha restituito questa persona, ma questo browser non ne ha traccia.",
+ "known.noLabels": "nessuna etichetta",
+ "known.undo": "Annulla",
+ "known.reenrolSaved": "Registra di nuovo dalla copia salvata",
+ "known.enrolAgain": "Registra di nuovo",
+ "known.play": "Riproduci il campione salvato di {{name}}",
+ "known.forget": "Rimuovi {{name}}",
+ "known.note": "Questo elenco è conservato in questo browser. Il server non ha una chiamata per elencare, quindi non si può verificare finché non esegui una ricerca.",
+ "known.noteSearched": "Questo elenco è conservato in questo browser. L'ultima ricerca lo ha confrontato con il server; una persona che non è stata restituita è segnalata.",
+ "stored.title": "Cosa viene conservato e dove",
+ "enrol.close": "Chiudi",
+ "enrol.cancel": "Annulla",
+ "enrol.submit": "Registra",
+ "enrol.working": "Registrazione in corso",
+ "enrol.ready": "Pronto per registrare.",
+ "enrol.stillNeeded": "Manca ancora: {{list}}.",
+ "enrol.needModel": "un modello",
+ "enrol.needRecording": "una registrazione",
+ "enrol.needPhoto": "una foto",
+ "enrol.needName": "un nome",
+ "enrol.needTranscript": "una trascrizione",
+ "enrol.needPermission": "il permesso",
+ "enrol.recordingStep": "Registrazione",
+ "enrol.photoStep": "Foto",
+ "enrol.recordingLabel": "Registrazione",
+ "enrol.photoLabel": "Foto",
+ "enrol.whoStep": "Chi è",
+ "enrol.name": "Nome",
+ "enrol.namePlaceholder": "es. Marta Rossi",
+ "enrol.labels": "Etichette",
+ "enrol.labelsHint": "facoltative, una per riga come chiave: valore",
+ "enrol.asSpeech": "Conserva anche come voce di sintesi",
+ "enrol.asSpeechHint": "La sintesi vocale potrà poi parlare con questa voce. Serve la trascrizione esatta.",
+ "enrol.transcript": "Trascrizione esatta",
+ "enrol.permissionStep": "Permesso",
+ "enrol.agreed": "Questa persona ha acconsentito",
+ "enrol.keep": "Conserva una copia in questo browser",
+ "toast.enrolled": "{{name}} registrato",
+ "toast.enrolledBoth": "{{name}} registrato e voce di sintesi salvata",
+ "toast.tooBig": "La copia era troppo grande per la memoria del browser, quindi non è stata conservata.",
+ "toast.speechFailed": "Registrazione riuscita, ma la voce di sintesi non è stata salvata: {{message}}",
+ "toast.removing": "Rimozione di {{name}}…",
+ "toast.removed": "{{name}} rimosso",
+ "toast.alreadyGone": "{{name}} non c'era già più sul server.",
+ "error.retry": "Riprova",
+ "error.traces": "Apri le tracce",
+ "needed.badge": "Non attivo su questo server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installa",
+ "needed.installing": "Installazione",
+ "needed.started": "Installazione di {{name}}. L'avanzamento è in Operazioni.",
+ "needed.loadFailed": "Non è stato possibile elencare i modelli.",
+ "needed.noneListed": "Nelle gallerie non c'è nessun modello corrispondente.",
+ "needed.browse": "Sfoglia tutti i modelli",
+ "needed.askAdmin": "Chiedi a un amministratore di installarne uno.",
+ "needed.whatTitle": "Cosa lo attiva",
+ "off.badge": "Non attivo per il tuo account",
+ "off.voice.title": "Il riconoscimento vocale è disattivato per il tuo account",
+ "off.voice.text": "Un amministratore non ti ha consentito di usare il riconoscimento vocale.",
+ "off.voice.what": "Un amministratore può attivare il permesso di riconoscimento vocale per il tuo utente in Utenti e chiavi.",
+ "off.face.title": "Il riconoscimento facciale è disattivato per il tuo account",
+ "off.face.text": "Un amministratore non ti ha consentito di usare il riconoscimento facciale.",
+ "off.face.what": "Un amministratore può attivare il permesso di riconoscimento facciale per il tuo utente in Utenti e chiavi."
+}
diff --git a/addons/face/ui/src/i18n/face.ko.json b/addons/face/ui/src/i18n/face.ko.json
new file mode 100644
index 0000000000..dfebdb34a3
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.ko.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "얼굴",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "진행 중",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "검색 중",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "제거",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "취소",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "중지",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "미리보기",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "이름",
+ "known.added": "추가됨",
+ "known.actions": "작업",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "실행 취소",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "{{name}} 제거",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "닫기",
+ "enrol.cancel": "취소",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "이름",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "다시 시도",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "설치",
+ "needed.installing": "설치 중",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/face.pt-BR.json b/addons/face/ui/src/i18n/face.pt-BR.json
new file mode 100644
index 0000000000..55a034be41
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.pt-BR.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "Rostos",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "Em andamento",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "Pesquisando",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Gravar",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Remover",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Cancelar",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Parar",
+ "clip.start": "Iniciar gravação",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Pré-visualização",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nome",
+ "known.added": "Adicionado",
+ "known.actions": "Ações",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Desfazer",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Remover {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "Fechar",
+ "enrol.cancel": "Cancelar",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nome",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Transcrição exata",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Tentar novamente",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Instalar",
+ "needed.installing": "Instalando",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Explorar todos os modelos",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/face.zh-CN.json b/addons/face/ui/src/i18n/face.zh-CN.json
new file mode 100644
index 0000000000..91f630d010
--- /dev/null
+++ b/addons/face/ui/src/i18n/face.zh-CN.json
@@ -0,0 +1,187 @@
+{
+ "faces.title": "人脸",
+ "faces.lede": "Find out whose face is in a photo, or whether two photos show the same person. Photos go only to the model running on this server.",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "进行中",
+ "face.person": "person",
+ "face.print": "faceprint",
+ "face.whoTab": "Who is this",
+ "face.whoLede": "Matches a photo against the people you enrolled.",
+ "face.sameLede": "Two photos, one question. Nobody has to be enrolled.",
+ "face.whoGo": "Find who this is",
+ "face.working": "正在搜索",
+ "face.whoNote": "Searches the people enrolled on this server. The photo you test with is not stored.",
+ "face.sameNote": "The answer comes from one faceprint per photo. Nothing is stored.",
+ "face.workingText": "Comparing the photo with the faceprints the server holds.",
+ "face.probably": "Probably {{name}}",
+ "face.matchText": "{{name}}'s faceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "face.nobody": "Nobody enrolled looks like this",
+ "face.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new person, or a photo that is blurred, turned away or in poor light.",
+ "face.noneEnrolled": "Nobody is enrolled to compare with",
+ "face.noneEnrolledText": "Enrol a person first, then look up a photo against them.",
+ "face.noneReturned": "The server returned no people",
+ "face.noneReturnedText": "It holds no faceprints made with this model. If the server restarted, enrolled faces are gone: enrol them again.",
+ "face.enrolThis": "Enrol this face",
+ "face.same": "Probably the same person",
+ "face.different": "Probably different people",
+ "face.knownTitle": "Known people",
+ "face.enrol": "Enrol a person",
+ "face.enrolTitle": "Enrol a person",
+ "face.enrolIntro": "The server keeps a faceprint and a name. It does not keep the photo.",
+ "face.emptyTitle": "Nobody enrolled in this browser yet",
+ "face.emptyText": "Enrol a person with one clear photo, then test a photo against them.",
+ "face.agreedHint": "I have their permission, or another lawful basis, to store a faceprint of them.",
+ "face.keepHint": "Lets you see the photo and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "face.antiSpoof": "Check for photos of photos",
+ "face.antiSpoofHint": "Needs a model that supports it.",
+ "face.real": "Real",
+ "face.spoof": "Spoof",
+ "face.found": "Face in photo {{n}}",
+ "face.neededTitle": "Faces need a face model",
+ "face.neededText": "Face recognition turns a photo into a faceprint and compares faceprints. It runs on a face-recognition model, and none is installed on this server.",
+ "face.neededWhat": "Install a model that supports face recognition. Once one is installed, this page turns on by itself. Users also need the Face recognition permission.",
+ "face.moreTitle": "More tools",
+ "face.moreHint": "detect faces, guess traits, get the raw faceprint",
+ "face.why.0": "The server matches one face per photo. A group photo is matched on a single face.",
+ "face.why.1": "Light, angle, glasses and blur change a face a lot.",
+ "face.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "face.stored.0.where": "On the server",
+ "face.stored.0.what": "A {{print}} (numbers that describe the face, not the picture), a name and your labels. They are kept in memory and are lost when the server restarts. The photo is not kept.",
+ "face.stored.1.where": "In this browser",
+ "face.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the photo only if you tick Keep a copy when enrolling.",
+ "face.stored.2.where": "Photos you test with",
+ "face.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "移除",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "取消",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "停止",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "预览",
+ "clip.photoAlt": "The photo you chose",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "名称",
+ "known.added": "添加时间",
+ "known.actions": "操作",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "撤销",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "移除 {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "stored.title": "What is stored, and where",
+ "enrol.close": "关闭",
+ "enrol.cancel": "取消",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "名称",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "重试",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "安装",
+ "needed.installing": "正在安装",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "off.face.title": "Face recognition is off for your account",
+ "off.face.text": "An administrator has not allowed you to use face recognition.",
+ "off.face.what": "An administrator can turn on the Face recognition permission for your user in Users and keys."
+}
diff --git a/addons/face/ui/src/i18n/index.js b/addons/face/ui/src/i18n/index.js
new file mode 100644
index 0000000000..4254058e9c
--- /dev/null
+++ b/addons/face/ui/src/i18n/index.js
@@ -0,0 +1,16 @@
+// The words of the face pages, one JSON bundle per language of the web UI.
+// English is the source and the fallback of every key. Italian is a draft
+// that wants a native review; a language whose bundle repeats the English
+// text of a key has no translation for it yet. addons/internal/i18ncheck
+// checks that every bundle has the keys and placeholders of English.
+import en from './face.en.json'
+import it from './face.it.json'
+import de from './face.de.json'
+import es from './face.es.json'
+import fr from './face.fr.json'
+import id from './face.id.json'
+import ko from './face.ko.json'
+import pt_BR from './face.pt-BR.json'
+import zh_CN from './face.zh-CN.json'
+
+export const locales = { en, it, de, es, fr, id, ko, 'pt-BR': pt_BR, 'zh-CN': zh_CN }
diff --git a/addons/face/ui/test/recognition.test.mjs b/addons/face/ui/test/recognition.test.mjs
index 701e8d6b19..12595f7dd7 100644
--- a/addons/face/ui/test/recognition.test.mjs
+++ b/addons/face/ui/test/recognition.test.mjs
@@ -69,7 +69,7 @@ test('recognition: the bundle has the face canvas, the face strings and no host
test('recognition: every translation key the sources read is in the Face bundle', async () => {
const { readFileSync, readdirSync, statSync } = await import('node:fs')
- const { default: en } = await import('../src/i18n/en.js')
+ const en = JSON.parse(readFileSync(join(here, '../src/i18n/face.en.json'), 'utf8'))
const roots = [join(here, '../src'), join(here, '../../../internal/identity-ui/src')]
const files = []
const walk = (d) => { for (const f of readdirSync(d)) { const p = join(d, f); if (statSync(p).isDirectory()) walk(p); else if (/\.jsx?$/.test(p) && !p.includes('i18n')) files.push(p) } }
diff --git a/addons/fine-tuning/ui/src/ctx.jsx b/addons/fine-tuning/ui/src/ctx.jsx
index e559efb73b..fb5ca9f090 100644
--- a/addons/fine-tuning/ui/src/ctx.jsx
+++ b/addons/fine-tuning/ui/src/ctx.jsx
@@ -1,4 +1,4 @@
-import en from './i18n/en.js'
+import { locales } from './i18n/index.js'
import { modelFrom as pickModel } from './lib/model.js'
// The one object every factory of the fine-tuning add-on receives. It binds the
@@ -10,7 +10,9 @@ export function createContext(sdk) {
const { React } = sdk
const h = React.createElement
const Fragment = React.Fragment
- sdk.locale.define('en', Object.fromEntries(Object.entries(en).filter(([, v]) => typeof v === 'string')))
+ // Every language of the page; a key a language lacks reads in English.
+ for (const [language, bundle] of Object.entries(locales)) sdk.locale.define(language, bundle)
+ const en = locales.en
const t = (key, vars) => sdk.locale.t(key, key, vars)
// The text of a key, or the fallback when the bundle has none.
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.de.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.de.json
new file mode 100644
index 0000000000..1da57925cc
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.de.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Fortschritt",
+ "steps.setup": "Einrichten",
+ "steps.check": "Prüfen",
+ "steps.run": "Ausführen",
+ "steps.result": "Ergebnis",
+ "run.stop": "Stoppen",
+ "run.connecting": "Verbindung zum Job wird hergestellt. Warten auf das erste Fortschrittsereignis.",
+ "run.progress": "Fortschritt",
+ "run.stages": "Phasen",
+ "run.step": "Schritt",
+ "run.of": "von",
+ "run.epoch": "Epoche",
+ "run.tokens": "Token/s",
+ "run.eta": "noch etwa {{time}}",
+ "run.etaSeconds": "noch etwa {{seconds}} s",
+ "checks.heading": "Vor dem Start prüfen",
+ "checks.live": "Wird aktualisiert, während Sie die Einrichtung ändern.",
+ "checks.meterLabel": "{{used}} Prozent belegt, {{free}} von {{total}} frei",
+ "checks.openBackends": "Backends öffnen",
+ "checks.barOk": "Nichts blockiert den Start.",
+ "checks.barWarn_one": "{{count}} Warnung. Sie können trotzdem starten.",
+ "checks.barWarn_other": "{{count}} Warnungen. Sie können trotzdem starten.",
+ "checks.title.model": "Modell",
+ "checks.title.data": "Datensatz",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Speicher",
+ "checks.title.disk": "Festplatte",
+ "checks.title.reward": "Belohnungsfunktionen",
+ "checks.title.resume": "Fortsetzen",
+ "checks.title.access": "Modellzugriff",
+ "checks.title.type": "Quantisierungstyp",
+ "checks.title.source": "Quelle",
+ "checks.title.name": "Name",
+ "checks.fineTune.modelOk": "Training von {{model}}.",
+ "checks.fineTune.modelMissing": "Geben Sie das Modell ein, das trainiert werden soll.",
+ "checks.fineTune.dataOk": "Die Daten kommen von {{source}}.",
+ "checks.fineTune.dataMissing": "Fügen Sie einen Datensatz hinzu: eine Hugging-Face-ID oder eine hochgeladene Datei.",
+ "checks.fineTune.backendOk": "{{name}} ist installiert.",
+ "checks.fineTune.backendMissing": "Kein Backend für Feinabstimmung ist installiert, der Job kann daher nicht starten.",
+ "checks.fineTune.backendOther": "{{name}} gehört nicht zu den installierten Backends für Feinabstimmung ({{list}}).",
+ "checks.fineTune.backendUnknown": "Die installierten Backends konnten nicht gelesen werden.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} von {{total}} frei. Batch-Größe {{batch}}, Sequenzlänge {{seq}}, Gradientenakkumulation {{accum}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab. LocalAI schätzt es vor dem Start des Jobs nicht.",
+ "checks.fineTune.memoryCluster": "{{total}} auf dem besten Knoten ({{node}}). Batch-Größe {{batch}}, Sequenzlänge {{seq}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab.",
+ "checks.fineTune.memoryCpu": "Keine GPU gefunden. Das Training läuft auf der CPU, und {{free}} von {{total}} RAM sind frei. Rechnen Sie mit Langsamkeit. Batch-Größe {{batch}}, Sequenzlänge {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Dieses Konto kann den Speicher des Rechners nicht lesen. Batch-Größe {{batch}}, Sequenzlänge {{seq}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab.",
+ "checks.fineTune.disk": "{{free}} frei auf dem Modell-Speicher. Downloads und Checkpoints brauchen Platz.",
+ "checks.fineTune.diskLow": "Nur {{free}} frei auf dem Modell-Speicher. Downloads und Checkpoints brauchen Platz.",
+ "checks.fineTune.rewardMissing": "GRPO braucht mindestens eine Belohnungsfunktion. Fügen Sie unter Weitere Optionen eine hinzu.",
+ "checks.fineTune.resume": "Das Training wird ab {{path}} fortgesetzt.",
+ "checks.fineTune.accessNone": "Kein Token gesetzt. Ein geschütztes Modell braucht ein Hugging-Face-Token unter Weitere Optionen.",
+ "checks.fineTune.accessSet": "Ein Hugging-Face-Token ist gesetzt.",
+ "checks.quantize.modelOk": "Quantisierung von {{model}}.",
+ "checks.quantize.modelMissing": "Geben Sie das Modell ein, das quantisiert werden soll.",
+ "checks.quantize.typeMissing": "Geben Sie den eigenen Quantisierungstyp ein.",
+ "checks.quantize.backendOk": "{{name}} ist installiert.",
+ "checks.quantize.backendMissing": "Kein Quantisierungs-Backend ist installiert, der Job kann daher nicht starten.",
+ "checks.quantize.backendOther": "{{name}} gehört nicht zu den installierten Quantisierungs-Backends ({{list}}).",
+ "checks.quantize.backendUnknown": "Die installierten Backends konnten nicht gelesen werden.",
+ "checks.quantize.memory": "{{free}} von {{total}} RAM sind frei. Die Umwandlung läuft auf der CPU.",
+ "checks.quantize.disk": "{{free}} frei auf dem Modell-Speicher. Download, umgewandelte Datei und Ergebnis werden auf die Festplatte geschrieben.",
+ "checks.quantize.diskLow": "Nur {{free}} frei auf dem Modell-Speicher. Download, umgewandelte Datei und Ergebnis werden auf die Festplatte geschrieben.",
+ "checks.quantize.accessNone": "Kein Token gesetzt. Ein geschütztes Modell braucht ein Hugging-Face-Token unter Weitere Optionen.",
+ "checks.quantize.accessSet": "Ein Hugging-Face-Token ist gesetzt.",
+ "checks.import.sourceOk_huggingface": "Erkannt als Hugging-Face-Repository. LocalAI prüft seine Dateien beim Import.",
+ "checks.import.sourceOk_url": "URL-Format erkannt. Der Inhalt wurde nicht geprüft.",
+ "checks.import.sourceOk_config": "Erkannt als Konfigurationsdatei eines Modells. Sie wird so gelesen und verwendet, wie sie ist.",
+ "checks.import.sourceOk_oci": "Erkannt als OCI-Image-Referenz.",
+ "checks.import.sourceOk_ollama": "Erkannt als Ollama-Modellreferenz.",
+ "checks.import.sourceOk_local": "Erkannt als Pfad auf dem Rechner, auf dem LocalAI läuft.",
+ "checks.import.sourceOk": "Die Quelle ist erkannt.",
+ "checks.import.sourceUnknown": "Das sieht nach keinem Format aus, das LocalAI kennt. Es wird trotzdem versucht und kann fehlschlagen.",
+ "checks.import.backendDownload": "{{name}} ist noch nicht installiert. LocalAI lädt es zuerst herunter.",
+ "checks.import.backendChosen": "{{name}} ist ausgewählt und wird installiert.",
+ "checks.import.backendAuto": "LocalAI wählt das Backend anhand der Dateien der Quelle, sobald der Import beginnt.",
+ "checks.import.nameTaken": "Ein Modell namens {{name}} ist bereits installiert.",
+ "checks.import.nameFree": "{{name}} ist nicht in Verwendung.",
+ "checks.import.nameAuto": "Das Modell erhält den Namen der Quelle.",
+ "checks.import.disk": "{{free}} frei auf dem Modell-Speicher. Die Download-Größe ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_gpu": "{{free}} GPU-Speicher sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_ram": "{{free}} RAM sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_cluster": "{{free}} sind auf dem besten Knoten verfügbar. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory": "{{free}} Speicher sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "form.key": "Schlüssel",
+ "form.value": "Wert",
+ "form.addOption": "Option hinzufügen",
+ "form.copy": "In die Zwischenablage kopieren",
+ "form.more": "Weitere Optionen",
+ "form.moreHint": "Methode, Backend, Adapter, Optimierer, Auswertung, Belohnungsfunktionen",
+ "form.baseModel": "Basismodell",
+ "form.model": "Modell",
+ "form.modelHint": "Eine Hugging-Face-ID oder ein Pfad auf diesem Host.",
+ "form.data": "Ihre Daten",
+ "form.dataset": "Quelle des Datensatzes",
+ "form.datasetHint": "Eine Hugging-Face-Datensatz-ID, oder laden Sie eine Datei hoch.",
+ "form.split": "Aufteilung",
+ "form.upload": "Stattdessen eine Datei hochladen",
+ "form.uploadHint": ".json, .jsonl oder .csv",
+ "form.removeFile": "Datei entfernen",
+ "form.train": "Was trainiert werden soll",
+ "form.kind": "Art des Trainings",
+ "form.kinds.adapter.title": "Adapter",
+ "form.kinds.adapter.text": "Trainiert einen kleinen Adapter und lässt die Basisgewichte unverändert. Die übliche Wahl, und sie braucht wenig Speicher.",
+ "form.kinds.full.title": "Vollständiges Modell",
+ "form.kinds.full.text": "Ändert jedes Gewicht. Es braucht ein Vielfaches des Speichers eines Adapters.",
+ "form.epochs": "Epochen",
+ "form.batch": "Batch-Größe",
+ "form.lr": "Lernrate",
+ "form.backend": "Backend",
+ "form.method": "Trainingsmethode",
+ "form.adapterKind": "Adaptertyp",
+ "form.rank": "Rang",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Schritte der Gradientenakkumulation",
+ "form.warmup": "Warmup-Schritte",
+ "form.seqLen": "Maximale Sequenzlänge",
+ "form.optimizer": "Optimierer",
+ "form.maxSteps": "Maximale Schritte",
+ "form.maxStepsHint": "0 für automatisch.",
+ "form.saveSteps": "Speicherschritte",
+ "form.saveLimit": "Gesamtlimit für Speicherungen",
+ "form.saveLimitHint": "0 behält jeden Checkpoint.",
+ "form.weightDecay": "Gewichtsabnahme",
+ "form.seed": "Seed",
+ "form.seedHint": "0 wählt einen zufälligen Seed.",
+ "form.precision": "Gemischte Genauigkeit",
+ "form.auto": "Automatisch",
+ "form.targetModules": "Zielmodule",
+ "form.targetModulesHint": "Durch Kommas getrennt. Leer lassen für den Standard des Backends.",
+ "form.gradCheckpointing": "Gradient Checkpointing (langsamer, braucht weniger Speicher)",
+ "form.token": "Hugging-Face-Token",
+ "form.tokenHint": "Nur für geschützte Modelle nötig.",
+ "form.evalEnable": "Während des Trainings auswerten",
+ "form.evalStrategy": "Auswertungsstrategie",
+ "form.evalSteps_": "Schritte",
+ "form.evalEpoch": "Epoche",
+ "form.evalSteps": "Auswertungsschritte",
+ "form.evalStepsHint": "0 entspricht den Speicherschritten.",
+ "form.evalSplit": "Aufteilung für die Auswertung",
+ "form.evalDataset": "Datensatz für die Auswertung",
+ "form.evalDatasetPlaceholder": "Ein eigener Datensatz",
+ "form.evalRatio": "Verhältnis der automatischen Aufteilung",
+ "form.rewards": "Belohnungsfunktionen (GRPO)",
+ "form.rewardsHint": "GRPO braucht mindestens eine Belohnungsfunktion. Wählen Sie eingebaute aus oder fügen Sie eigene hinzu.",
+ "form.resume": "Vom Checkpoint fortsetzen",
+ "form.clearCheckpoint": "Checkpoint löschen",
+ "form.extra": "Zusätzliche Optionen",
+ "form.extraHint": "Schlüssel-Wert-Paare, die vom Backend abhängen.",
+ "form.summaryFull": "vollständiges Modell",
+ "form.summary_lora": "LoRA-Adapter",
+ "form.summary_loha": "LoHa-Adapter",
+ "form.summary_lokr": "LoKr-Adapter",
+ "form.recipe": "Trainiert einen {{kind}} von {{model}} mit {{method}}, {{epochs}} Epochen über {{data}}.",
+ "form.recipeEmpty": "Wählen Sie ein Modell und einen Datensatz, um zu sehen, was trainiert wird.",
+ "rewards.format_reward": "Prüft das Format ... gefolgt von der Antwort.",
+ "rewards.reasoning_accuracy_reward": "Vergleicht den Inhalt von mit der Antwortspalte des Datensatzes.",
+ "rewards.length_reward": "Bewertet, wie nah die Antwort an einer Ziellänge liegt.",
+ "rewards.xml_tag_reward": "Bewertet korrekt geöffnete und geschlossene XML-Tags.",
+ "rewards.no_repetition_reward": "Bestraft wiederholte n-Gramme.",
+ "rewards.code_execution_reward": "Prüft, ob Python-Codeblöcke gültige Syntax haben.",
+ "rewards.param_target_length": "Ziellänge",
+ "rewards.fnName": "Funktionsname",
+ "rewards.fnBody": "Funktionskörper",
+ "rewards.fnHint": "Erhält completions und kwargs und muss eine Liste von Gleitkommazahlen zurückgeben. re, math, json und string sind verfügbar.",
+ "rewards.add": "Hinzufügen",
+ "rewards.cancel": "Abbrechen",
+ "rewards.custom": "Eigene Belohnungsfunktion hinzufügen",
+ "fineTune.title": "Feinabstimmung",
+ "fineTune.lede": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn als Modell, mit dem Sie chatten können.",
+ "fineTune.importConfig": "Konfiguration importieren",
+ "fineTune.exportConfig": "Konfiguration exportieren",
+ "fineTune.newJob": "Neuer Job",
+ "fineTune.badConfig": "Diese Datei konnte nicht gelesen werden. Verwenden Sie eine JSON-Konfiguration, die auf dieser Seite exportiert wurde.",
+ "fineTune.start": "Feinabstimmung starten",
+ "fineTune.starting": "Wird gestartet...",
+ "fineTune.resume": "Training fortsetzen",
+ "fineTune.resuming": "Wird vom Checkpoint fortgesetzt:",
+ "fineTune.clear": "Leeren",
+ "fineTune.barNote": "Nichts wird geändert, bis Sie starten.",
+ "fineTune.jobMeta": "{{kind}} {{method}} auf {{backend}}, gestartet {{started}}",
+ "fineTune.failed": "Training fehlgeschlagen",
+ "fineTune.failedAt": "Training bei Schritt {{step}} von {{total}} gestoppt",
+ "fineTune.noMessage": "Das Backend hat keine Meldung ausgegeben.",
+ "fineTune.finished": "Training abgeschlossen.",
+ "fineTune.finishedLoss": "Training abgeschlossen. Der letzte Loss war {{loss}}.",
+ "fineTune.stopped": "Der Job wurde gestoppt. Ein Checkpoint bleibt erhalten, wenn Sie das gewählt haben.",
+ "fineTune.tryTitle": "Was als Nächstes zu tun ist",
+ "fineTune.tryText": "Der Server meldet, dass der Speicher nicht mehr reicht. Jede Änderung unten wird auf eine Kopie der Einrichtung angewendet, und nichts startet, bis Sie Start drücken.",
+ "fineTune.tryPlain": "Öffnen Sie die Einrichtung erneut, ändern Sie, worauf die Meldung hinweist, und starten Sie einen neuen Job.",
+ "fineTune.fixBatch": "Batch-Größe auf 1 senken",
+ "fineTune.fixCheckpointing": "Gradient Checkpointing einschalten (langsamer, braucht weniger Speicher)",
+ "fineTune.retryChanges": "Einrichtung mit diesen Änderungen öffnen",
+ "fineTune.reuse": "Einrichtung erneut öffnen",
+ "fineTune.resumeFrom": "Ab Schritt {{step}} fortsetzen",
+ "fineTune.status.queued": "In der Warteschlange",
+ "fineTune.status.loading_model": "Modell wird geladen",
+ "fineTune.status.loading_dataset": "Datensatz wird geladen",
+ "fineTune.status.training": "Training",
+ "fineTune.status.saving": "Speichern",
+ "fineTune.status.completed": "Abgeschlossen",
+ "fineTune.status.failed": "Fehlgeschlagen",
+ "fineTune.status.stopped": "Gestoppt",
+ "stop.title": "Training stoppen?",
+ "stop.text": "Ein jetzt gespeicherter Checkpoint erlaubt es, das bisher Gelernte fortzusetzen oder zu exportieren.",
+ "stop.keep": "Stoppen und einen Checkpoint behalten",
+ "stop.discard": "Ohne Checkpoint stoppen",
+ "stop.cancel": "Training fortsetzen",
+ "stop.cancelDelete": "Abbrechen",
+ "chart.loss": "Loss",
+ "chart.learning_rate": "Lernrate",
+ "chart.grad_norm": "Gradientennorm",
+ "chart.metric": "Metrik",
+ "chart.step": "Schritt",
+ "chart.eval": "Auswertungs-Loss",
+ "chart.waiting": "Warten auf weitere Schritte.",
+ "chart.window": "Schritte {{from}} bis {{to}} von {{total}}",
+ "chart.windowEval": "Schritte {{from}} bis {{to}} von {{total}}. Die hohlen Punkte sind der Auswertungs-Loss.",
+ "chart.aria": "{{metric}} über die Trainingsschritte. Der letzte Wert ist {{last}} bei Schritt {{step}}.",
+ "chart.readout": "Schritt {{step}}: {{value}}",
+ "chart.dataTitle": "Die Zahlen anzeigen",
+ "chart.emptyText": "Das Diagramm wird gezeichnet, sobald der Job zwei Schritte gemeldet hat.",
+ "log.title": "Protokoll",
+ "log.note": "Was der Job seit dem Öffnen dieser Seite gemeldet hat.",
+ "log.only": "Nur Warnungen und Fehler",
+ "log.copy": "Kopieren",
+ "log.copied": "Kopiert",
+ "log.download": "Herunterladen",
+ "log.empty": "Noch nichts gemeldet.",
+ "log.noneFiltered": "Keine Warnungen oder Fehler.",
+ "checkpoints.title": "Checkpoints",
+ "checkpoints.step": "Schritt",
+ "checkpoints.epoch": "Epoche",
+ "checkpoints.loss": "Loss",
+ "checkpoints.path": "Pfad",
+ "checkpoints.actions": "Aktionen",
+ "checkpoints.resume": "Fortsetzen",
+ "checkpoints.resumeTitle": "Training ab diesem Checkpoint fortsetzen",
+ "checkpoints.export": "Exportieren",
+ "checkpoints.exportTitle": "Diesen Checkpoint exportieren",
+ "export.blocked": "Der Export ist auf diesem Server ausgeschaltet, weil er den Modellordner nicht mit dem Add-on-Host teilt.",
+ "export.title": "Als Modell exportieren",
+ "export.lede": "Beim Exportieren wird das Ergebnis in ein Modell umgewandelt, das LocalAI laden kann.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Endmodell (Ausgabeverzeichnis)",
+ "export.checkpointOption": "Schritt {{step}} (Loss {{loss}})",
+ "export.format": "Exportformat",
+ "export.lora": "LoRA-Adapter",
+ "export.merged16": "Zusammengeführt (16 Bit)",
+ "export.merged4": "Zusammengeführt (4 Bit)",
+ "export.quant": "Quantisierung",
+ "export.name": "Modellname",
+ "export.nameHint": "Leer lassen, um einen Namen aus dem Job zu erzeugen.",
+ "export.go": "Exportieren",
+ "export.exporting": "Wird exportiert...",
+ "export.working": "Export läuft...",
+ "export.done": "Modell exportiert und als \"{{name}}\" registriert",
+ "export.failed": "Export fehlgeschlagen: {{message}}",
+ "export.unknown": "unbekannter Fehler",
+ "export.nextTitle": "{{name}} ist jetzt ein Modell",
+ "export.nextText": "Es ist bei LocalAI registriert. Öffnen Sie einen Chat damit oder finden Sie es unter Modelle.",
+ "export.chat": "Mit {{name}} chatten",
+ "export.models": "Unter Modelle öffnen",
+ "export.archive": "Archiv herunterladen",
+ "jobs.title": "Frühere Jobs",
+ "jobs.count_one": "{{count}} auf diesem Server gespeichert",
+ "jobs.count_other": "{{count}} auf diesem Server gespeichert",
+ "jobs.loading": "Jobs werden geladen",
+ "jobs.emptyTitle": "Noch keine Feinabstimmungs-Jobs",
+ "jobs.emptyText": "Richten Sie oben einen ein. Er erscheint hier mit seinem Status, und Sie können seine Einrichtung später wiederverwenden.",
+ "jobs.model": "Modell",
+ "jobs.kind": "Art",
+ "jobs.status": "Status",
+ "jobs.started": "Gestartet",
+ "jobs.actions": "Aktionen",
+ "jobs.open": "Diesen Job öffnen",
+ "jobs.reuse": "Wiederverwenden",
+ "jobs.reuseTitle": "Die Einrichtung dieses Jobs für einen neuen Job verwenden",
+ "jobs.delete": "Löschen",
+ "jobs.deleteTitle": "Diesen Job und seine Daten löschen",
+ "jobs.deleteDialogTitle": "Diesen Job löschen?",
+ "jobs.deleteDialogText": "Dies löscht den Job mit seinen Checkpoints und einem exportierten Modell. Das kann nicht rückgängig gemacht werden.",
+ "off.badge": "Für Sie nicht verfügbar",
+ "off.fineTune.title": "Ihr Konto kann keine Feinabstimmung",
+ "off.fineTune.text": "Feinabstimmung ist eine eigene Berechtigung, und dieses Konto hat sie nicht.",
+ "off.quantize.title": "Ihr Konto kann nicht quantisieren",
+ "off.quantize.text": "Quantisierung ist eine eigene Berechtigung, und dieses Konto hat sie nicht.",
+ "off.admin": "Ein Administrator kann sie unter Benutzer und Schlüssel aktivieren.",
+ "off.askAdmin": "Bitten Sie einen Administrator, sie für Ihr Konto zu aktivieren.",
+ "off.back": "Zur Startseite",
+ "landing.experimental": "Experimentell",
+ "run.unknownPercent": "Fortschritt noch nicht gemeldet",
+ "export.adminOnly.title": "Das Exportieren eines Modells ist Administratoren vorbehalten",
+ "export.adminOnly.text": "Beim Exportieren wird ein Modell zu diesem Server hinzugefügt, für alle, die es nutzen dürfen. Bitte einen Administrator, diesen Auftrag zu exportieren."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/en.js b/addons/fine-tuning/ui/src/i18n/fine-tuning.en.json
similarity index 99%
rename from addons/fine-tuning/ui/src/i18n/en.js
rename to addons/fine-tuning/ui/src/i18n/fine-tuning.en.json
index 9b3229ba9b..d14df76741 100644
--- a/addons/fine-tuning/ui/src/i18n/en.js
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.en.json
@@ -1,6 +1,4 @@
-// Generated once from the host locale file public/locales/en/tools.json; edit by hand from here on.
-// The last three keys belong to this add-on.
-export default {
+{
"steps.label": "Progress",
"steps.setup": "Set up",
"steps.check": "Check",
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.es.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.es.json
new file mode 100644
index 0000000000..d54edf461c
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.es.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Progreso",
+ "steps.setup": "Configurar",
+ "steps.check": "Comprobar",
+ "steps.run": "Ejecutar",
+ "steps.result": "Resultado",
+ "run.stop": "Detener",
+ "run.connecting": "Conectando con el job. Esperando el primer evento de progreso.",
+ "run.progress": "Progreso",
+ "run.stages": "Etapas",
+ "run.step": "paso",
+ "run.of": "de",
+ "run.epoch": "época",
+ "run.tokens": "tokens/s",
+ "run.eta": "faltan unos {{time}}",
+ "run.etaSeconds": "faltan unos {{seconds}} s",
+ "checks.heading": "Comprueba antes de empezar",
+ "checks.live": "Se actualiza mientras cambias la configuración.",
+ "checks.meterLabel": "{{used}} por ciento en uso, {{free}} libres de {{total}}",
+ "checks.openBackends": "Abrir Backends",
+ "checks.barOk": "Nada bloquea el inicio.",
+ "checks.barWarn_one": "{{count}} aviso. Aun así puedes empezar.",
+ "checks.barWarn_other": "{{count}} avisos. Aun así puedes empezar.",
+ "checks.title.model": "Modelo",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memoria",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funciones de recompensa",
+ "checks.title.resume": "Reanudar",
+ "checks.title.access": "Acceso al modelo",
+ "checks.title.type": "Tipo de cuantización",
+ "checks.title.source": "Origen",
+ "checks.title.name": "Nombre",
+ "checks.fineTune.modelOk": "Entrenando {{model}}.",
+ "checks.fineTune.modelMissing": "Escribe el modelo que quieres entrenar.",
+ "checks.fineTune.dataOk": "Los datos vienen de {{source}}.",
+ "checks.fineTune.dataMissing": "Añade un dataset: un id de Hugging Face o un archivo subido.",
+ "checks.fineTune.backendOk": "{{name}} está instalado.",
+ "checks.fineTune.backendMissing": "No hay ningún backend de ajuste instalado, así que el job podría no arrancar.",
+ "checks.fineTune.backendOther": "{{name}} no está entre los backends de ajuste instalados ({{list}}).",
+ "checks.fineTune.backendUnknown": "No se pudieron leer los backends instalados.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} libres de {{total}}. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}, acumulación de gradiente {{accum}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes, y LocalAI no lo estima antes de que el job arranque.",
+ "checks.fineTune.memoryCluster": "{{total}} en el mejor nodo ({{node}}). Tamaño de lote {{batch}}, longitud de secuencia {{seq}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes.",
+ "checks.fineTune.memoryCpu": "No se encontró ninguna GPU. El entrenamiento usa la CPU y {{free}} de {{total}} de RAM están libres. Espera que sea lento. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Esta cuenta no puede leer la memoria de la máquina. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes.",
+ "checks.fineTune.disk": "{{free}} libres en el disco de modelos. Las descargas y los checkpoints necesitan espacio.",
+ "checks.fineTune.diskLow": "Solo {{free}} libres en el disco de modelos. Las descargas y los checkpoints necesitan espacio.",
+ "checks.fineTune.rewardMissing": "GRPO necesita al menos una función de recompensa. Añade una en Más opciones.",
+ "checks.fineTune.resume": "El entrenamiento continúa desde {{path}}.",
+ "checks.fineTune.accessNone": "No hay token definido. Un modelo restringido necesita un token de Hugging Face en Más opciones.",
+ "checks.fineTune.accessSet": "Hay un token de Hugging Face definido.",
+ "checks.quantize.modelOk": "Cuantizando {{model}}.",
+ "checks.quantize.modelMissing": "Escribe el modelo que quieres cuantizar.",
+ "checks.quantize.typeMissing": "Escribe el tipo de cuantización personalizado.",
+ "checks.quantize.backendOk": "{{name}} está instalado.",
+ "checks.quantize.backendMissing": "No hay ningún backend de cuantización instalado, así que el job podría no arrancar.",
+ "checks.quantize.backendOther": "{{name}} no está entre los backends de cuantización instalados ({{list}}).",
+ "checks.quantize.backendUnknown": "No se pudieron leer los backends instalados.",
+ "checks.quantize.memory": "{{free}} libres de {{total}} de RAM. La conversión se ejecuta en la CPU.",
+ "checks.quantize.disk": "{{free}} libres en el disco de modelos. La descarga, el archivo convertido y el resultado se escriben en disco.",
+ "checks.quantize.diskLow": "Solo {{free}} libres en el disco de modelos. La descarga, el archivo convertido y el resultado se escriben en disco.",
+ "checks.quantize.accessNone": "No hay token definido. Un modelo restringido necesita un token de Hugging Face en Más opciones.",
+ "checks.quantize.accessSet": "Hay un token de Hugging Face definido.",
+ "checks.import.sourceOk_huggingface": "Se reconoce como repositorio de Hugging Face. LocalAI revisa sus archivos al importar.",
+ "checks.import.sourceOk_url": "Formato de URL reconocido. Su contenido no se ha comprobado.",
+ "checks.import.sourceOk_config": "Se reconoce como archivo de configuración de un modelo. Se lee y se usa tal como está.",
+ "checks.import.sourceOk_oci": "Se reconoce como referencia de imagen OCI.",
+ "checks.import.sourceOk_ollama": "Se reconoce como referencia de modelo de Ollama.",
+ "checks.import.sourceOk_local": "Se reconoce como ruta en la máquina que ejecuta LocalAI.",
+ "checks.import.sourceOk": "El origen se reconoce.",
+ "checks.import.sourceUnknown": "Esto no parece ninguna forma que LocalAI conozca. Lo intentará igualmente y podría fallar.",
+ "checks.import.backendDownload": "{{name}} aún no está instalado. LocalAI lo descarga primero.",
+ "checks.import.backendChosen": "{{name}} está elegido e instalado.",
+ "checks.import.backendAuto": "LocalAI elige el backend según los archivos del origen cuando empieza la importación.",
+ "checks.import.nameTaken": "Ya hay instalado un modelo llamado {{name}}.",
+ "checks.import.nameFree": "{{name}} no está en uso.",
+ "checks.import.nameAuto": "El modelo toma el nombre del origen.",
+ "checks.import.disk": "{{free}} libres en el disco de modelos. El tamaño de la descarga se conoce cuando empieza la importación.",
+ "checks.import.memory_gpu": "Ahora hay {{free}} libres de memoria de GPU. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory_ram": "Ahora hay {{free}} libres de RAM. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory_cluster": "Hay {{free}} disponibles en el mejor nodo. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory": "Ahora hay {{free}} libres de memoria. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "form.key": "Clave",
+ "form.value": "Valor",
+ "form.addOption": "Añadir opción",
+ "form.copy": "Copiar al portapapeles",
+ "form.more": "Más opciones",
+ "form.moreHint": "método, backend, adaptador, optimizador, evaluación, funciones de recompensa",
+ "form.baseModel": "Modelo base",
+ "form.model": "Modelo",
+ "form.modelHint": "Un id de Hugging Face, o una ruta en este host.",
+ "form.data": "Tus datos",
+ "form.dataset": "Origen del dataset",
+ "form.datasetHint": "Un id de dataset de Hugging Face, o sube un archivo.",
+ "form.split": "Partición",
+ "form.upload": "Subir un archivo en su lugar",
+ "form.uploadHint": ".json, .jsonl o .csv",
+ "form.removeFile": "Quitar el archivo",
+ "form.train": "Qué entrenar",
+ "form.kind": "Tipo de entrenamiento",
+ "form.kinds.adapter.title": "Adaptador",
+ "form.kinds.adapter.text": "Entrena un adaptador pequeño y deja intactos los pesos base. Es la opción habitual, y necesita poca memoria.",
+ "form.kinds.full.title": "Modelo completo",
+ "form.kinds.full.text": "Cambia cada peso. Necesita varias veces la memoria de un adaptador.",
+ "form.epochs": "Épocas",
+ "form.batch": "Tamaño de lote",
+ "form.lr": "Tasa de aprendizaje",
+ "form.backend": "Backend",
+ "form.method": "Método de entrenamiento",
+ "form.adapterKind": "Tipo de adaptador",
+ "form.rank": "Rango",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Pasos de acumulación de gradiente",
+ "form.warmup": "Pasos de calentamiento",
+ "form.seqLen": "Longitud máxima de secuencia",
+ "form.optimizer": "Optimizador",
+ "form.maxSteps": "Pasos máximos",
+ "form.maxStepsHint": "0 para automático.",
+ "form.saveSteps": "Pasos de guardado",
+ "form.saveLimit": "Límite total de guardados",
+ "form.saveLimitHint": "0 conserva cada checkpoint.",
+ "form.weightDecay": "Decaimiento de pesos",
+ "form.seed": "Semilla",
+ "form.seedHint": "0 elige una semilla aleatoria.",
+ "form.precision": "Precisión mixta",
+ "form.auto": "Automático",
+ "form.targetModules": "Módulos objetivo",
+ "form.targetModulesHint": "Separados por comas. Déjalo en blanco para el valor por defecto del backend.",
+ "form.gradCheckpointing": "Gradient checkpointing (más lento, usa menos memoria)",
+ "form.token": "Token de Hugging Face",
+ "form.tokenHint": "Solo hace falta para los modelos restringidos.",
+ "form.evalEnable": "Evaluar durante el entrenamiento",
+ "form.evalStrategy": "Estrategia de evaluación",
+ "form.evalSteps_": "Pasos",
+ "form.evalEpoch": "Época",
+ "form.evalSteps": "Pasos de evaluación",
+ "form.evalStepsHint": "0 coincide con los pasos de guardado.",
+ "form.evalSplit": "Partición de evaluación",
+ "form.evalDataset": "Dataset de evaluación",
+ "form.evalDatasetPlaceholder": "Un dataset separado",
+ "form.evalRatio": "Proporción de partición automática",
+ "form.rewards": "Funciones de recompensa (GRPO)",
+ "form.rewardsHint": "GRPO necesita al menos una función de recompensa. Elige las integradas o añade las tuyas.",
+ "form.resume": "Reanudar desde un checkpoint",
+ "form.clearCheckpoint": "Borrar el checkpoint",
+ "form.extra": "Opciones extra",
+ "form.extraHint": "Pares clave y valor propios del backend.",
+ "form.summaryFull": "modelo completo",
+ "form.summary_lora": "adaptador LoRA",
+ "form.summary_loha": "adaptador LoHa",
+ "form.summary_lokr": "adaptador LoKr",
+ "form.recipe": "Entrenar un {{kind}} de {{model}} con {{method}}, {{epochs}} épocas sobre {{data}}.",
+ "form.recipeEmpty": "Elige un modelo y un dataset para ver qué se entrenará.",
+ "rewards.format_reward": "Comprueba el formato ... seguido de la respuesta.",
+ "rewards.reasoning_accuracy_reward": "Compara el contenido de con la columna de respuestas del dataset.",
+ "rewards.length_reward": "Puntúa lo cerca que está la respuesta de una longitud objetivo.",
+ "rewards.xml_tag_reward": "Puntúa las etiquetas XML abiertas y cerradas correctamente.",
+ "rewards.no_repetition_reward": "Penaliza los n-gramas repetidos.",
+ "rewards.code_execution_reward": "Comprueba que los bloques de código Python tengan una sintaxis válida.",
+ "rewards.param_target_length": "Longitud objetivo",
+ "rewards.fnName": "Nombre de la función",
+ "rewards.fnBody": "Cuerpo de la función",
+ "rewards.fnHint": "Recibe completions y kwargs y debe devolver una lista de floats. Están disponibles re, math, json y string.",
+ "rewards.add": "Añadir",
+ "rewards.cancel": "Cancelar",
+ "rewards.custom": "Añadir una función de recompensa propia",
+ "fineTune.title": "Ajuste fino",
+ "fineTune.lede": "Entrena un adaptador con tus propios datos y luego expórtalo como un modelo con el que puedas chatear.",
+ "fineTune.importConfig": "Importar configuración",
+ "fineTune.exportConfig": "Exportar configuración",
+ "fineTune.newJob": "Nuevo job",
+ "fineTune.badConfig": "No se pudo leer ese archivo. Usa una configuración JSON exportada desde esta página.",
+ "fineTune.start": "Iniciar el ajuste fino",
+ "fineTune.starting": "Iniciando...",
+ "fineTune.resume": "Reanudar el entrenamiento",
+ "fineTune.resuming": "Reanudando desde el checkpoint:",
+ "fineTune.clear": "Limpiar",
+ "fineTune.barNote": "No se cambia nada hasta que inicies.",
+ "fineTune.jobMeta": "{{kind}} {{method}} en {{backend}}, iniciado {{started}}",
+ "fineTune.failed": "El entrenamiento falló",
+ "fineTune.failedAt": "El entrenamiento se detuvo en el paso {{step}} de {{total}}",
+ "fineTune.noMessage": "El backend no dio ningún mensaje.",
+ "fineTune.finished": "El entrenamiento terminó.",
+ "fineTune.finishedLoss": "El entrenamiento terminó. La última pérdida fue {{loss}}.",
+ "fineTune.stopped": "El job se detuvo. Se conserva un checkpoint si lo elegiste.",
+ "fineTune.tryTitle": "Qué hacer después",
+ "fineTune.tryText": "El servidor indicó que se acabó la memoria. Cada cambio de abajo se aplica a una copia de la configuración, y nada arranca hasta que pulses Iniciar.",
+ "fineTune.tryPlain": "Vuelve a abrir la configuración, cambia lo que indica el mensaje e inicia un job nuevo.",
+ "fineTune.fixBatch": "Bajar el tamaño de lote a 1",
+ "fineTune.fixCheckpointing": "Activar gradient checkpointing (más lento, usa menos memoria)",
+ "fineTune.retryChanges": "Abrir la configuración con estos cambios",
+ "fineTune.reuse": "Volver a abrir la configuración",
+ "fineTune.resumeFrom": "Reanudar desde el paso {{step}}",
+ "fineTune.status.queued": "En cola",
+ "fineTune.status.loading_model": "Cargando el modelo",
+ "fineTune.status.loading_dataset": "Cargando el dataset",
+ "fineTune.status.training": "Entrenando",
+ "fineTune.status.saving": "Guardando",
+ "fineTune.status.completed": "Completado",
+ "fineTune.status.failed": "Fallido",
+ "fineTune.status.stopped": "Detenido",
+ "stop.title": "¿Detener el entrenamiento?",
+ "stop.text": "Un checkpoint guardado ahora te permite reanudar o exportar lo aprendido hasta ahora.",
+ "stop.keep": "Detener y guardar un checkpoint",
+ "stop.discard": "Detener sin checkpoint",
+ "stop.cancel": "Seguir entrenando",
+ "stop.cancelDelete": "Cancelar",
+ "chart.loss": "Pérdida",
+ "chart.learning_rate": "Tasa de aprendizaje",
+ "chart.grad_norm": "Norma del gradiente",
+ "chart.metric": "Métrica",
+ "chart.step": "paso",
+ "chart.eval": "Pérdida de evaluación",
+ "chart.waiting": "Esperando más pasos.",
+ "chart.window": "Pasos {{from}} a {{to}} de {{total}}",
+ "chart.windowEval": "Pasos {{from}} a {{to}} de {{total}}. Los puntos vacíos son la pérdida de evaluación.",
+ "chart.aria": "{{metric}} a lo largo de los pasos de entrenamiento. El último valor es {{last}} en el paso {{step}}.",
+ "chart.readout": "paso {{step}}: {{value}}",
+ "chart.dataTitle": "Mostrar los números",
+ "chart.emptyText": "El gráfico se dibuja cuando el job ha informado de dos pasos.",
+ "log.title": "Registro",
+ "log.note": "Lo que el job ha informado desde que se abrió esta página.",
+ "log.only": "Solo avisos y errores",
+ "log.copy": "Copiar",
+ "log.copied": "Copiado",
+ "log.download": "Descargar",
+ "log.empty": "Aún no se ha informado de nada.",
+ "log.noneFiltered": "No hay avisos ni errores.",
+ "checkpoints.title": "Checkpoints",
+ "checkpoints.step": "Paso",
+ "checkpoints.epoch": "Época",
+ "checkpoints.loss": "Pérdida",
+ "checkpoints.path": "Ruta",
+ "checkpoints.actions": "Acciones",
+ "checkpoints.resume": "Reanudar",
+ "checkpoints.resumeTitle": "Reanudar el entrenamiento desde este checkpoint",
+ "checkpoints.export": "Exportar",
+ "checkpoints.exportTitle": "Exportar este checkpoint",
+ "export.blocked": "La exportación está desactivada en este servidor porque no comparte la carpeta de modelos con el host del complemento.",
+ "export.title": "Exportar como modelo",
+ "export.lede": "Exportar convierte el resultado en un modelo que LocalAI puede cargar.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Modelo final (directorio de salida)",
+ "export.checkpointOption": "Paso {{step}} (pérdida {{loss}})",
+ "export.format": "Formato de exportación",
+ "export.lora": "Adaptador LoRA",
+ "export.merged16": "Fusionado (16 bits)",
+ "export.merged4": "Fusionado (4 bits)",
+ "export.quant": "Cuantización",
+ "export.name": "Nombre del modelo",
+ "export.nameHint": "Déjalo en blanco para generar uno a partir del job.",
+ "export.go": "Exportar",
+ "export.exporting": "Exportando...",
+ "export.working": "Exportación en curso...",
+ "export.done": "Modelo exportado y registrado como \"{{name}}\"",
+ "export.failed": "La exportación falló: {{message}}",
+ "export.unknown": "error desconocido",
+ "export.nextTitle": "{{name}} ya es un modelo",
+ "export.nextText": "Está registrado en LocalAI. Abre un chat con él o búscalo en Modelos.",
+ "export.chat": "Chatear con {{name}}",
+ "export.models": "Abrir en Modelos",
+ "export.archive": "Descargar el archivo",
+ "jobs.title": "Jobs anteriores",
+ "jobs.count_one": "{{count}} guardado en este servidor",
+ "jobs.count_other": "{{count}} guardados en este servidor",
+ "jobs.loading": "Cargando jobs",
+ "jobs.emptyTitle": "Aún no hay jobs de ajuste fino",
+ "jobs.emptyText": "Configura uno arriba. Aparece aquí con su estado, y puedes reutilizar su configuración más adelante.",
+ "jobs.model": "Modelo",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Estado",
+ "jobs.started": "Iniciado",
+ "jobs.actions": "Acciones",
+ "jobs.open": "Abrir este job",
+ "jobs.reuse": "Reutilizar",
+ "jobs.reuseTitle": "Usar la configuración de este job para uno nuevo",
+ "jobs.delete": "Eliminar",
+ "jobs.deleteTitle": "Eliminar este job y sus datos",
+ "jobs.deleteDialogTitle": "¿Eliminar este job?",
+ "jobs.deleteDialogText": "Esto elimina el job con sus checkpoints y cualquier modelo exportado. No se puede deshacer.",
+ "off.badge": "No disponible para ti",
+ "off.fineTune.title": "Tu cuenta no puede hacer ajuste fino",
+ "off.fineTune.text": "El ajuste fino es un permiso aparte, y esta cuenta no lo tiene.",
+ "off.quantize.title": "Tu cuenta no puede cuantizar",
+ "off.quantize.text": "La cuantización es un permiso aparte, y esta cuenta no lo tiene.",
+ "off.admin": "Un administrador puede activarlo en Usuarios y claves.",
+ "off.askAdmin": "Pide a un administrador que lo active para tu cuenta.",
+ "off.back": "Ir al inicio",
+ "landing.experimental": "Experimental",
+ "run.unknownPercent": "Progreso aún no informado",
+ "export.adminOnly.title": "Exportar un modelo es cosa de administradores",
+ "export.adminOnly.text": "Exportar añade un modelo a este servidor para todos los que pueden usarlo. Pide a un administrador que exporte este trabajo."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.fr.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.fr.json
new file mode 100644
index 0000000000..52cf58e2ce
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.fr.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Progression",
+ "steps.setup": "Configuration",
+ "steps.check": "Vérification",
+ "steps.run": "Exécution",
+ "steps.result": "Résultat",
+ "run.stop": "Arrêter",
+ "run.connecting": "Connexion au job. En attente du premier événement de progression.",
+ "run.progress": "Progression",
+ "run.stages": "Étapes",
+ "run.step": "étape",
+ "run.of": "sur",
+ "run.epoch": "époque",
+ "run.tokens": "jetons/s",
+ "run.eta": "environ {{time}} restantes",
+ "run.etaSeconds": "environ {{seconds}} s restantes",
+ "checks.heading": "Vérifier avant de commencer",
+ "checks.live": "Se met à jour à mesure que vous modifiez la configuration.",
+ "checks.meterLabel": "{{used}} pour cent utilisés, {{free}} libres sur {{total}}",
+ "checks.openBackends": "Ouvrir Backends",
+ "checks.barOk": "Rien ne bloque le démarrage.",
+ "checks.barWarn_one": "{{count}} avertissement. Vous pouvez tout de même démarrer.",
+ "checks.barWarn_other": "{{count}} avertissements. Vous pouvez tout de même démarrer.",
+ "checks.title.model": "Modèle",
+ "checks.title.data": "Jeu de données",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Mémoire",
+ "checks.title.disk": "Disque",
+ "checks.title.reward": "Fonctions de récompense",
+ "checks.title.resume": "Reprise",
+ "checks.title.access": "Accès au modèle",
+ "checks.title.type": "Type de quantification",
+ "checks.title.source": "Source",
+ "checks.title.name": "Nom",
+ "checks.fineTune.modelOk": "Entraînement de {{model}}.",
+ "checks.fineTune.modelMissing": "Saisissez le modèle à entraîner.",
+ "checks.fineTune.dataOk": "Les données viennent de {{source}}.",
+ "checks.fineTune.dataMissing": "Ajoutez un jeu de données : un identifiant Hugging Face ou un fichier envoyé.",
+ "checks.fineTune.backendOk": "{{name}} est installé.",
+ "checks.fineTune.backendMissing": "Aucun backend de fine-tuning n'est installé, la tâche risque donc de ne pas démarrer.",
+ "checks.fineTune.backendOther": "{{name}} ne fait pas partie des backends de fine-tuning installés ({{list}}).",
+ "checks.fineTune.backendUnknown": "Impossible de lire les backends installés.",
+ "checks.fineTune.memoryGpu": "{{name}} : {{free}} libres sur {{total}}. Taille de batch {{batch}}, longueur de séquence {{seq}}, accumulation de gradient {{accum}}. La mémoire nécessaire dépend du modèle et de ces réglages, et LocalAI ne l'estime pas avant le démarrage de la tâche.",
+ "checks.fineTune.memoryCluster": "{{total}} sur le meilleur nœud ({{node}}). Taille de batch {{batch}}, longueur de séquence {{seq}}. La mémoire nécessaire dépend du modèle et de ces réglages.",
+ "checks.fineTune.memoryCpu": "Aucun GPU trouvé. L'entraînement utilise le CPU, et {{free}} de RAM sur {{total}} sont libres. Attendez-vous à de la lenteur. Taille de batch {{batch}}, longueur de séquence {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Ce compte ne peut pas lire la mémoire de la machine. Taille de batch {{batch}}, longueur de séquence {{seq}}. La mémoire nécessaire dépend du modèle et de ces réglages.",
+ "checks.fineTune.disk": "{{free}} libres sur le disque des modèles. Les téléchargements et les checkpoints ont besoin d'espace.",
+ "checks.fineTune.diskLow": "Seulement {{free}} libres sur le disque des modèles. Les téléchargements et les checkpoints ont besoin d'espace.",
+ "checks.fineTune.rewardMissing": "GRPO nécessite au moins une fonction de récompense. Ajoutez-en une dans Plus d'options.",
+ "checks.fineTune.resume": "L'entraînement reprend depuis {{path}}.",
+ "checks.fineTune.accessNone": "Aucun jeton défini. Un modèle protégé nécessite un jeton Hugging Face dans Plus d'options.",
+ "checks.fineTune.accessSet": "Un jeton Hugging Face est défini.",
+ "checks.quantize.modelOk": "Quantification de {{model}}.",
+ "checks.quantize.modelMissing": "Saisissez le modèle à quantifier.",
+ "checks.quantize.typeMissing": "Saisissez le type de quantification personnalisé.",
+ "checks.quantize.backendOk": "{{name}} est installé.",
+ "checks.quantize.backendMissing": "Aucun backend de quantification n'est installé, la tâche risque donc de ne pas démarrer.",
+ "checks.quantize.backendOther": "{{name}} ne fait pas partie des backends de quantification installés ({{list}}).",
+ "checks.quantize.backendUnknown": "Impossible de lire les backends installés.",
+ "checks.quantize.memory": "{{free}} de RAM sur {{total}} sont libres. La conversion se fait sur le CPU.",
+ "checks.quantize.disk": "{{free}} libres sur le disque des modèles. Le téléchargement, le fichier converti et le résultat sont écrits sur le disque.",
+ "checks.quantize.diskLow": "Seulement {{free}} libres sur le disque des modèles. Le téléchargement, le fichier converti et le résultat sont écrits sur le disque.",
+ "checks.quantize.accessNone": "Aucun jeton défini. Un modèle protégé nécessite un jeton Hugging Face dans Plus d'options.",
+ "checks.quantize.accessSet": "Un jeton Hugging Face est défini.",
+ "checks.import.sourceOk_huggingface": "Reconnu comme dépôt Hugging Face. LocalAI examine ses fichiers lors de l'import.",
+ "checks.import.sourceOk_url": "Format d'URL reconnu. Son contenu n'a pas été vérifié.",
+ "checks.import.sourceOk_config": "Reconnu comme fichier de configuration de modèle. Il est lu et utilisé tel quel.",
+ "checks.import.sourceOk_oci": "Reconnu comme référence d'image OCI.",
+ "checks.import.sourceOk_ollama": "Reconnu comme référence de modèle Ollama.",
+ "checks.import.sourceOk_local": "Reconnu comme chemin sur la machine qui exécute LocalAI.",
+ "checks.import.sourceOk": "La source est reconnue.",
+ "checks.import.sourceUnknown": "Ce format ne correspond à aucun format connu de LocalAI. L'import essaiera quand même, et peut échouer.",
+ "checks.import.backendDownload": "{{name}} n'est pas encore installé. LocalAI le télécharge d'abord.",
+ "checks.import.backendChosen": "{{name}} est choisi et installé.",
+ "checks.import.backendAuto": "LocalAI choisit le backend d'après les fichiers de la source au démarrage de l'import.",
+ "checks.import.nameTaken": "Un modèle nommé {{name}} est déjà installé.",
+ "checks.import.nameFree": "{{name}} n'est pas utilisé.",
+ "checks.import.nameAuto": "Le modèle prend le nom de la source.",
+ "checks.import.disk": "{{free}} libres sur le disque des modèles. La taille du téléchargement est connue au démarrage de l'import.",
+ "checks.import.memory_gpu": "{{free}} de mémoire GPU sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory_ram": "{{free}} de RAM sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory_cluster": "{{free}} sont disponibles sur le meilleur nœud. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory": "{{free}} de mémoire sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "form.key": "Clé",
+ "form.value": "Valeur",
+ "form.addOption": "Ajouter une option",
+ "form.copy": "Copier dans le presse-papiers",
+ "form.more": "Plus d'options",
+ "form.moreHint": "méthode, backend, adaptateur, optimiseur, évaluation, fonctions de récompense",
+ "form.baseModel": "Modèle de base",
+ "form.model": "Modèle",
+ "form.modelHint": "Un identifiant Hugging Face, ou un chemin sur cette machine.",
+ "form.data": "Vos données",
+ "form.dataset": "Source du jeu de données",
+ "form.datasetHint": "Un identifiant de jeu de données Hugging Face, ou envoyez un fichier.",
+ "form.split": "Découpage",
+ "form.upload": "Envoyer un fichier à la place",
+ "form.uploadHint": ".json, .jsonl ou .csv",
+ "form.removeFile": "Retirer le fichier",
+ "form.train": "Ce qu'il faut entraîner",
+ "form.kind": "Type d'entraînement",
+ "form.kinds.adapter.title": "Adaptateur",
+ "form.kinds.adapter.text": "Entraîne un petit adaptateur et laisse les poids de base intacts. Le choix habituel, qui demande peu de mémoire.",
+ "form.kinds.full.title": "Modèle complet",
+ "form.kinds.full.text": "Modifie chaque poids. Il demande plusieurs fois la mémoire d'un adaptateur.",
+ "form.epochs": "Époques",
+ "form.batch": "Taille de batch",
+ "form.lr": "Taux d'apprentissage",
+ "form.backend": "Backend",
+ "form.method": "Méthode d'entraînement",
+ "form.adapterKind": "Type d'adaptateur",
+ "form.rank": "Rang",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Pas d'accumulation de gradient",
+ "form.warmup": "Pas de préchauffage",
+ "form.seqLen": "Longueur maximale de séquence",
+ "form.optimizer": "Optimiseur",
+ "form.maxSteps": "Nombre maximal de pas",
+ "form.maxStepsHint": "0 pour automatique.",
+ "form.saveSteps": "Pas de sauvegarde",
+ "form.saveLimit": "Limite totale de sauvegardes",
+ "form.saveLimitHint": "0 conserve chaque checkpoint.",
+ "form.weightDecay": "Décroissance des poids",
+ "form.seed": "Graine",
+ "form.seedHint": "0 choisit une graine aléatoire.",
+ "form.precision": "Précision mixte",
+ "form.auto": "Automatique",
+ "form.targetModules": "Modules cibles",
+ "form.targetModulesHint": "Séparés par des virgules. Laissez vide pour la valeur par défaut du backend.",
+ "form.gradCheckpointing": "Gradient checkpointing (plus lent, utilise moins de mémoire)",
+ "form.token": "Jeton Hugging Face",
+ "form.tokenHint": "Nécessaire uniquement pour les modèles protégés.",
+ "form.evalEnable": "Évaluer pendant l'entraînement",
+ "form.evalStrategy": "Stratégie d'évaluation",
+ "form.evalSteps_": "Pas",
+ "form.evalEpoch": "Époque",
+ "form.evalSteps": "Pas d'évaluation",
+ "form.evalStepsHint": "0 correspond aux pas de sauvegarde.",
+ "form.evalSplit": "Découpage pour l'évaluation",
+ "form.evalDataset": "Jeu de données d'évaluation",
+ "form.evalDatasetPlaceholder": "Un jeu de données séparé",
+ "form.evalRatio": "Ratio de découpage automatique",
+ "form.rewards": "Fonctions de récompense (GRPO)",
+ "form.rewardsHint": "GRPO nécessite au moins une fonction de récompense. Choisissez les fonctions intégrées ou ajoutez les vôtres.",
+ "form.resume": "Reprendre depuis un checkpoint",
+ "form.clearCheckpoint": "Effacer le checkpoint",
+ "form.extra": "Options supplémentaires",
+ "form.extraHint": "Paires clé-valeur propres au backend.",
+ "form.summaryFull": "modèle complet",
+ "form.summary_lora": "adaptateur LoRA",
+ "form.summary_loha": "adaptateur LoHa",
+ "form.summary_lokr": "adaptateur LoKr",
+ "form.recipe": "Entraîner un {{kind}} de {{model}} avec {{method}}, {{epochs}} époques sur {{data}}.",
+ "form.recipeEmpty": "Choisissez un modèle et un jeu de données pour voir ce qui sera entraîné.",
+ "rewards.format_reward": "Vérifie le format ... suivi de la réponse.",
+ "rewards.reasoning_accuracy_reward": "Compare le contenu de à la colonne de réponses du jeu de données.",
+ "rewards.length_reward": "Note la proximité de la réponse avec une longueur cible.",
+ "rewards.xml_tag_reward": "Note les balises XML correctement ouvertes et fermées.",
+ "rewards.no_repetition_reward": "Pénalise les n-grammes répétés.",
+ "rewards.code_execution_reward": "Vérifie que les blocs de code Python ont une syntaxe valide.",
+ "rewards.param_target_length": "Longueur cible",
+ "rewards.fnName": "Nom de la fonction",
+ "rewards.fnBody": "Corps de la fonction",
+ "rewards.fnHint": "Reçoit completions et kwargs et doit renvoyer une liste de flottants. re, math, json et string sont disponibles.",
+ "rewards.add": "Ajouter",
+ "rewards.cancel": "Annuler",
+ "rewards.custom": "Ajouter une fonction de récompense personnalisée",
+ "fineTune.title": "Fine-tuning",
+ "fineTune.lede": "Entraînez un adaptateur sur vos propres données, puis exportez-le comme modèle avec lequel vous pouvez discuter.",
+ "fineTune.importConfig": "Importer la configuration",
+ "fineTune.exportConfig": "Exporter la configuration",
+ "fineTune.newJob": "Nouvelle tâche",
+ "fineTune.badConfig": "Impossible de lire ce fichier. Utilisez une configuration JSON exportée depuis cette page.",
+ "fineTune.start": "Lancer le fine-tuning",
+ "fineTune.starting": "Démarrage...",
+ "fineTune.resume": "Reprendre l'entraînement",
+ "fineTune.resuming": "Reprise depuis le checkpoint :",
+ "fineTune.clear": "Effacer",
+ "fineTune.barNote": "Rien n'est modifié tant que vous ne lancez pas.",
+ "fineTune.jobMeta": "{{kind}} {{method}} sur {{backend}}, démarré {{started}}",
+ "fineTune.failed": "Échec de l'entraînement",
+ "fineTune.failedAt": "Entraînement arrêté à l'étape {{step}} sur {{total}}",
+ "fineTune.noMessage": "Le backend n'a donné aucun message.",
+ "fineTune.finished": "Entraînement terminé.",
+ "fineTune.finishedLoss": "Entraînement terminé. La dernière perte était {{loss}}.",
+ "fineTune.stopped": "La tâche a été arrêtée. Un checkpoint est conservé si vous l'avez choisi.",
+ "fineTune.tryTitle": "Que faire ensuite",
+ "fineTune.tryText": "Le serveur indique que la mémoire est épuisée. Chaque modification ci-dessous est appliquée à une copie de la configuration, et rien ne démarre tant que vous n'appuyez pas sur Lancer.",
+ "fineTune.tryPlain": "Rouvrez la configuration, modifiez ce que le message indique et lancez une nouvelle tâche.",
+ "fineTune.fixBatch": "Réduire la taille de batch à 1",
+ "fineTune.fixCheckpointing": "Activer le gradient checkpointing (plus lent, utilise moins de mémoire)",
+ "fineTune.retryChanges": "Ouvrir la configuration avec ces modifications",
+ "fineTune.reuse": "Rouvrir la configuration",
+ "fineTune.resumeFrom": "Reprendre à partir de l'étape {{step}}",
+ "fineTune.status.queued": "En file d'attente",
+ "fineTune.status.loading_model": "Chargement du modèle",
+ "fineTune.status.loading_dataset": "Chargement du jeu de données",
+ "fineTune.status.training": "Entraînement",
+ "fineTune.status.saving": "Sauvegarde",
+ "fineTune.status.completed": "Terminée",
+ "fineTune.status.failed": "Échouée",
+ "fineTune.status.stopped": "Arrêtée",
+ "stop.title": "Arrêter l'entraînement ?",
+ "stop.text": "Un checkpoint sauvegardé maintenant permet de reprendre ou d'exporter ce qui a été appris jusqu'ici.",
+ "stop.keep": "Arrêter et garder un checkpoint",
+ "stop.discard": "Arrêter sans checkpoint",
+ "stop.cancel": "Continuer l'entraînement",
+ "stop.cancelDelete": "Annuler",
+ "chart.loss": "Perte",
+ "chart.learning_rate": "Taux d'apprentissage",
+ "chart.grad_norm": "Norme du gradient",
+ "chart.metric": "Métrique",
+ "chart.step": "étape",
+ "chart.eval": "Perte d'évaluation",
+ "chart.waiting": "En attente de plus d'étapes.",
+ "chart.window": "Étapes {{from}} à {{to}} sur {{total}}",
+ "chart.windowEval": "Étapes {{from}} à {{to}} sur {{total}}. Les points vides sont la perte d'évaluation.",
+ "chart.aria": "{{metric}} au fil des étapes d'entraînement. La dernière valeur est {{last}} à l'étape {{step}}.",
+ "chart.readout": "étape {{step}} : {{value}}",
+ "chart.dataTitle": "Afficher les valeurs",
+ "chart.emptyText": "Le graphique se trace lorsque la tâche a signalé deux étapes.",
+ "log.title": "Journal",
+ "log.note": "Ce que la tâche a signalé depuis l'ouverture de cette page.",
+ "log.only": "Avertissements et erreurs uniquement",
+ "log.copy": "Copier",
+ "log.copied": "Copié",
+ "log.download": "Télécharger",
+ "log.empty": "Rien n'a encore été signalé.",
+ "log.noneFiltered": "Aucun avertissement ni erreur.",
+ "checkpoints.title": "Checkpoints",
+ "checkpoints.step": "Étape",
+ "checkpoints.epoch": "Époque",
+ "checkpoints.loss": "Perte",
+ "checkpoints.path": "Chemin",
+ "checkpoints.actions": "Actions",
+ "checkpoints.resume": "Reprendre",
+ "checkpoints.resumeTitle": "Reprendre l'entraînement depuis ce checkpoint",
+ "checkpoints.export": "Exporter",
+ "checkpoints.exportTitle": "Exporter ce checkpoint",
+ "export.blocked": "L'export est désactivé sur ce serveur, car il ne partage pas le dossier des modèles avec l'hôte de l'extension.",
+ "export.title": "Exporter comme modèle",
+ "export.lede": "L'export transforme le résultat en modèle que LocalAI peut charger.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Modèle final (dossier de sortie)",
+ "export.checkpointOption": "Étape {{step}} (perte {{loss}})",
+ "export.format": "Format d'export",
+ "export.lora": "Adaptateur LoRA",
+ "export.merged16": "Fusionné (16 bits)",
+ "export.merged4": "Fusionné (4 bits)",
+ "export.quant": "Quantification",
+ "export.name": "Nom du modèle",
+ "export.nameHint": "Laissez vide pour en générer un à partir de la tâche.",
+ "export.go": "Exporter",
+ "export.exporting": "Export en cours...",
+ "export.working": "Export en cours...",
+ "export.done": "Modèle exporté et enregistré sous « {{name}} »",
+ "export.failed": "Échec de l'export : {{message}}",
+ "export.unknown": "erreur inconnue",
+ "export.nextTitle": "{{name}} est maintenant un modèle",
+ "export.nextText": "Il est enregistré dans LocalAI. Ouvrez une discussion avec lui, ou retrouvez-le dans Modèles.",
+ "export.chat": "Discuter avec {{name}}",
+ "export.models": "Ouvrir dans Modèles",
+ "export.archive": "Télécharger l'archive",
+ "jobs.title": "Tâches précédentes",
+ "jobs.count_one": "{{count}} conservée sur ce serveur",
+ "jobs.count_other": "{{count}} conservées sur ce serveur",
+ "jobs.loading": "Chargement des tâches",
+ "jobs.emptyTitle": "Aucune tâche de fine-tuning pour le moment",
+ "jobs.emptyText": "Configurez-en une ci-dessus. Elle apparaît ici avec son état, et vous pourrez réutiliser sa configuration plus tard.",
+ "jobs.model": "Modèle",
+ "jobs.kind": "Type",
+ "jobs.status": "État",
+ "jobs.started": "Démarré",
+ "jobs.actions": "Actions",
+ "jobs.open": "Ouvrir cette tâche",
+ "jobs.reuse": "Réutiliser",
+ "jobs.reuseTitle": "Utiliser la configuration de cette tâche pour une nouvelle tâche",
+ "jobs.delete": "Supprimer",
+ "jobs.deleteTitle": "Supprimer cette tâche et ses données",
+ "jobs.deleteDialogTitle": "Supprimer cette tâche ?",
+ "jobs.deleteDialogText": "Cela supprime la tâche avec ses checkpoints et tout modèle exporté. Action irréversible.",
+ "off.badge": "Non disponible pour vous",
+ "off.fineTune.title": "Votre compte ne peut pas faire de fine-tuning",
+ "off.fineTune.text": "Le fine-tuning est une autorisation distincte, que ce compte n'a pas.",
+ "off.quantize.title": "Votre compte ne peut pas quantifier",
+ "off.quantize.text": "La quantification est une autorisation distincte, que ce compte n'a pas.",
+ "off.admin": "Un administrateur peut l'activer dans Utilisateurs et clés.",
+ "off.askAdmin": "Demandez à un administrateur de l'activer pour votre compte.",
+ "off.back": "Aller à l'accueil",
+ "landing.experimental": "Expérimental",
+ "run.unknownPercent": "Progression pas encore communiquée",
+ "export.adminOnly.title": "L'export d'un modèle est réservé aux administrateurs",
+ "export.adminOnly.text": "L'export ajoute un modèle à ce serveur pour tous ceux qui peuvent l'utiliser. Demandez à un administrateur d'exporter cette tâche."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.id.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.id.json
new file mode 100644
index 0000000000..ba93db6420
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.id.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Progres",
+ "steps.setup": "Siapkan",
+ "steps.check": "Periksa",
+ "steps.run": "Jalankan",
+ "steps.result": "Hasil",
+ "run.stop": "Hentikan",
+ "run.connecting": "Menghubungkan ke job. Menunggu event progres pertama.",
+ "run.progress": "Progres",
+ "run.stages": "Tahap",
+ "run.step": "langkah",
+ "run.of": "dari",
+ "run.epoch": "epoch",
+ "run.tokens": "token/s",
+ "run.eta": "sekitar {{time}} lagi",
+ "run.etaSeconds": "sekitar {{seconds}} d lagi",
+ "checks.heading": "Periksa sebelum memulai",
+ "checks.live": "Diperbarui saat Anda mengubah pengaturan.",
+ "checks.meterLabel": "{{used}} persen terpakai, {{free}} tersedia dari {{total}}",
+ "checks.openBackends": "Buka Backend",
+ "checks.barOk": "Tidak ada yang menghalangi permulaan.",
+ "checks.barWarn_one": "{{count}} peringatan. Anda tetap bisa memulai.",
+ "checks.barWarn_other": "{{count}} peringatan. Anda tetap bisa memulai.",
+ "checks.title.model": "Model",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memori",
+ "checks.title.disk": "Disk",
+ "checks.title.reward": "Fungsi reward",
+ "checks.title.resume": "Melanjutkan",
+ "checks.title.access": "Akses model",
+ "checks.title.type": "Tipe kuantisasi",
+ "checks.title.source": "Sumber",
+ "checks.title.name": "Nama",
+ "checks.fineTune.modelOk": "Melatih {{model}}.",
+ "checks.fineTune.modelMissing": "Masukkan model yang akan dilatih.",
+ "checks.fineTune.dataOk": "Data berasal dari {{source}}.",
+ "checks.fineTune.dataMissing": "Tambahkan dataset: id Hugging Face atau file yang diunggah.",
+ "checks.fineTune.backendOk": "{{name}} sudah terpasang.",
+ "checks.fineTune.backendMissing": "Belum ada backend fine-tuning yang terpasang, jadi job mungkin gagal dimulai.",
+ "checks.fineTune.backendOther": "{{name}} tidak termasuk backend fine-tuning yang terpasang ({{list}}).",
+ "checks.fineTune.backendUnknown": "Backend yang terpasang tidak dapat dibaca.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} dari {{total}} tersedia. Ukuran batch {{batch}}, panjang sekuens {{seq}}, akumulasi gradien {{accum}}. Kebutuhan job bergantung pada model dan pengaturan ini, dan LocalAI tidak memperkirakannya sebelum job dimulai.",
+ "checks.fineTune.memoryCluster": "{{total}} di node terbaik ({{node}}). Ukuran batch {{batch}}, panjang sekuens {{seq}}. Kebutuhan job bergantung pada model dan pengaturan ini.",
+ "checks.fineTune.memoryCpu": "GPU tidak ditemukan. Pelatihan menggunakan CPU dan {{free}} dari {{total}} RAM tersedia. Diperkirakan lambat. Ukuran batch {{batch}}, panjang sekuens {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Akun ini tidak dapat membaca memori mesin. Ukuran batch {{batch}}, panjang sekuens {{seq}}. Kebutuhan job bergantung pada model dan pengaturan ini.",
+ "checks.fineTune.disk": "{{free}} tersedia di disk model. Unduhan dan checkpoint membutuhkan ruang.",
+ "checks.fineTune.diskLow": "Hanya {{free}} tersedia di disk model. Unduhan dan checkpoint membutuhkan ruang.",
+ "checks.fineTune.rewardMissing": "GRPO membutuhkan minimal satu fungsi reward. Tambahkan satu di Opsi lainnya.",
+ "checks.fineTune.resume": "Pelatihan dilanjutkan dari {{path}}.",
+ "checks.fineTune.accessNone": "Token belum diatur. Model yang dibatasi membutuhkan token Hugging Face di Opsi lainnya.",
+ "checks.fineTune.accessSet": "Token Hugging Face sudah diatur.",
+ "checks.quantize.modelOk": "Mengkuantisasi {{model}}.",
+ "checks.quantize.modelMissing": "Masukkan model yang akan dikuantisasi.",
+ "checks.quantize.typeMissing": "Masukkan tipe kuantisasi kustom.",
+ "checks.quantize.backendOk": "{{name}} sudah terpasang.",
+ "checks.quantize.backendMissing": "Belum ada backend kuantisasi yang terpasang, jadi job mungkin gagal dimulai.",
+ "checks.quantize.backendOther": "{{name}} tidak termasuk backend kuantisasi yang terpasang ({{list}}).",
+ "checks.quantize.backendUnknown": "Backend yang terpasang tidak dapat dibaca.",
+ "checks.quantize.memory": "{{free}} dari {{total}} RAM tersedia. Konversi berjalan di CPU.",
+ "checks.quantize.disk": "{{free}} tersedia di disk model. Unduhan, file hasil konversi, dan hasilnya ditulis ke disk.",
+ "checks.quantize.diskLow": "Hanya {{free}} tersedia di disk model. Unduhan, file hasil konversi, dan hasilnya ditulis ke disk.",
+ "checks.quantize.accessNone": "Token belum diatur. Model yang dibatasi membutuhkan token Hugging Face di Opsi lainnya.",
+ "checks.quantize.accessSet": "Token Hugging Face sudah diatur.",
+ "checks.import.sourceOk_huggingface": "Dikenali sebagai repositori Hugging Face. LocalAI memeriksa file-filenya saat Anda mengimpor.",
+ "checks.import.sourceOk_url": "Format URL dikenali. Isinya belum diperiksa.",
+ "checks.import.sourceOk_config": "Dikenali sebagai file konfigurasi model. File dibaca dan digunakan apa adanya.",
+ "checks.import.sourceOk_oci": "Dikenali sebagai referensi image OCI.",
+ "checks.import.sourceOk_ollama": "Dikenali sebagai referensi model Ollama.",
+ "checks.import.sourceOk_local": "Dikenali sebagai path di mesin yang menjalankan LocalAI.",
+ "checks.import.sourceOk": "Sumber dikenali.",
+ "checks.import.sourceUnknown": "Ini tidak tampak seperti format yang diketahui LocalAI. Tetap akan dicoba, dan mungkin gagal.",
+ "checks.import.backendDownload": "{{name}} belum terpasang. LocalAI mengunduhnya terlebih dahulu.",
+ "checks.import.backendChosen": "{{name}} dipilih dan terpasang.",
+ "checks.import.backendAuto": "LocalAI memilih backend dari file sumber saat impor dimulai.",
+ "checks.import.nameTaken": "Model bernama {{name}} sudah terpasang.",
+ "checks.import.nameFree": "{{name}} belum digunakan.",
+ "checks.import.nameAuto": "Model dinamai sesuai sumbernya.",
+ "checks.import.disk": "{{free}} tersedia di disk model. Ukuran unduhan diketahui saat impor dimulai.",
+ "checks.import.memory_gpu": "{{free}} memori GPU tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory_ram": "{{free}} RAM tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory_cluster": "{{free}} tersedia di node terbaik. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory": "{{free}} memori tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "form.key": "Key",
+ "form.value": "Value",
+ "form.addOption": "Tambah opsi",
+ "form.copy": "Salin ke clipboard",
+ "form.more": "Opsi lainnya",
+ "form.moreHint": "metode, backend, adapter, optimizer, evaluasi, fungsi reward",
+ "form.baseModel": "Model dasar",
+ "form.model": "Model",
+ "form.modelHint": "Id Hugging Face, atau path di host ini.",
+ "form.data": "Data Anda",
+ "form.dataset": "Sumber dataset",
+ "form.datasetHint": "Id dataset Hugging Face, atau unggah file.",
+ "form.split": "Pembagian",
+ "form.upload": "Unggah file sebagai gantinya",
+ "form.uploadHint": ".json, .jsonl atau .csv",
+ "form.removeFile": "Hapus file",
+ "form.train": "Yang akan dilatih",
+ "form.kind": "Jenis pelatihan",
+ "form.kinds.adapter.title": "Adapter",
+ "form.kinds.adapter.text": "Melatih adapter kecil dan membiarkan bobot dasar tidak berubah. Pilihan yang umum, dan membutuhkan sedikit memori.",
+ "form.kinds.full.title": "Model penuh",
+ "form.kinds.full.text": "Mengubah setiap bobot. Membutuhkan memori beberapa kali lipat dari adapter.",
+ "form.epochs": "Epoch",
+ "form.batch": "Ukuran batch",
+ "form.lr": "Learning rate",
+ "form.backend": "Backend",
+ "form.method": "Metode pelatihan",
+ "form.adapterKind": "Jenis adapter",
+ "form.rank": "Rank",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Langkah akumulasi gradien",
+ "form.warmup": "Langkah warmup",
+ "form.seqLen": "Panjang sekuens maksimum",
+ "form.optimizer": "Optimizer",
+ "form.maxSteps": "Langkah maksimum",
+ "form.maxStepsHint": "0 untuk otomatis.",
+ "form.saveSteps": "Langkah simpan",
+ "form.saveLimit": "Batas total simpan",
+ "form.saveLimitHint": "0 menyimpan setiap checkpoint.",
+ "form.weightDecay": "Weight decay",
+ "form.seed": "Seed",
+ "form.seedHint": "0 memilih seed acak.",
+ "form.precision": "Presisi campuran",
+ "form.auto": "Otomatis",
+ "form.targetModules": "Modul target",
+ "form.targetModulesHint": "Dipisahkan koma. Kosongkan untuk default backend.",
+ "form.gradCheckpointing": "Gradient checkpointing (lebih lambat, memakai lebih sedikit memori)",
+ "form.token": "Token Hugging Face",
+ "form.tokenHint": "Hanya dibutuhkan untuk model yang dibatasi.",
+ "form.evalEnable": "Evaluasi selama pelatihan",
+ "form.evalStrategy": "Strategi evaluasi",
+ "form.evalSteps_": "Langkah",
+ "form.evalEpoch": "Epoch",
+ "form.evalSteps": "Langkah evaluasi",
+ "form.evalStepsHint": "0 mengikuti langkah simpan.",
+ "form.evalSplit": "Pembagian evaluasi",
+ "form.evalDataset": "Dataset evaluasi",
+ "form.evalDatasetPlaceholder": "Dataset terpisah",
+ "form.evalRatio": "Rasio pembagian otomatis",
+ "form.rewards": "Fungsi reward (GRPO)",
+ "form.rewardsHint": "GRPO membutuhkan minimal satu fungsi reward. Pilih yang bawaan atau tambahkan sendiri.",
+ "form.resume": "Lanjutkan dari checkpoint",
+ "form.clearCheckpoint": "Hapus checkpoint",
+ "form.extra": "Opsi tambahan",
+ "form.extraHint": "Pasangan kunci dan nilai khusus backend.",
+ "form.summaryFull": "model penuh",
+ "form.summary_lora": "adapter LoRA",
+ "form.summary_loha": "adapter LoHa",
+ "form.summary_lokr": "adapter LoKr",
+ "form.recipe": "Latih {{kind}} dari {{model}} dengan {{method}}, {{epochs}} epoch pada {{data}}.",
+ "form.recipeEmpty": "Pilih model dan dataset untuk melihat apa yang akan dilatih.",
+ "rewards.format_reward": "Memeriksa format ... lalu jawaban.",
+ "rewards.reasoning_accuracy_reward": "Membandingkan isi dengan kolom jawaban dataset.",
+ "rewards.length_reward": "Menilai seberapa dekat jawaban dengan panjang target.",
+ "rewards.xml_tag_reward": "Menilai tag XML yang dibuka dan ditutup dengan benar.",
+ "rewards.no_repetition_reward": "Memberi penalti pada n-gram yang berulang.",
+ "rewards.code_execution_reward": "Memeriksa bahwa blok kode Python memiliki sintaks yang valid.",
+ "rewards.param_target_length": "Panjang target",
+ "rewards.fnName": "Nama fungsi",
+ "rewards.fnBody": "Isi fungsi",
+ "rewards.fnHint": "Menerima completions dan kwargs dan harus mengembalikan daftar float. re, math, json dan string tersedia.",
+ "rewards.add": "Tambah",
+ "rewards.cancel": "Batal",
+ "rewards.custom": "Tambahkan fungsi reward kustom",
+ "fineTune.title": "Fine-tune",
+ "fineTune.lede": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang bisa Anda ajak chat.",
+ "fineTune.importConfig": "Impor konfigurasi",
+ "fineTune.exportConfig": "Ekspor konfigurasi",
+ "fineTune.newJob": "Job baru",
+ "fineTune.badConfig": "File tidak dapat dibaca. Gunakan konfigurasi JSON yang diekspor dari halaman ini.",
+ "fineTune.start": "Mulai fine-tuning",
+ "fineTune.starting": "Memulai...",
+ "fineTune.resume": "Lanjutkan pelatihan",
+ "fineTune.resuming": "Melanjutkan dari checkpoint:",
+ "fineTune.clear": "Bersihkan",
+ "fineTune.barNote": "Tidak ada yang berubah sampai Anda memulai.",
+ "fineTune.jobMeta": "{{kind}} {{method}} pada {{backend}}, dimulai {{started}}",
+ "fineTune.failed": "Pelatihan gagal",
+ "fineTune.failedAt": "Pelatihan berhenti pada langkah {{step}} dari {{total}}",
+ "fineTune.noMessage": "Backend tidak memberikan pesan.",
+ "fineTune.finished": "Pelatihan selesai.",
+ "fineTune.finishedLoss": "Pelatihan selesai. Loss terakhir adalah {{loss}}.",
+ "fineTune.stopped": "Job dihentikan. Checkpoint disimpan jika Anda memilih untuk menyimpannya.",
+ "fineTune.tryTitle": "Langkah berikutnya",
+ "fineTune.tryText": "Server melaporkan bahwa memori habis. Setiap perubahan di bawah diterapkan pada salinan pengaturan, dan tidak ada yang dimulai sampai Anda menekan Mulai.",
+ "fineTune.tryPlain": "Buka pengaturan lagi, ubah apa yang ditunjuk pesan, lalu mulai job baru.",
+ "fineTune.fixBatch": "Turunkan ukuran batch menjadi 1",
+ "fineTune.fixCheckpointing": "Aktifkan gradient checkpointing (lebih lambat, memakai lebih sedikit memori)",
+ "fineTune.retryChanges": "Buka pengaturan dengan perubahan ini",
+ "fineTune.reuse": "Buka pengaturan lagi",
+ "fineTune.resumeFrom": "Lanjutkan dari langkah {{step}}",
+ "fineTune.status.queued": "Antre",
+ "fineTune.status.loading_model": "Memuat model",
+ "fineTune.status.loading_dataset": "Memuat dataset",
+ "fineTune.status.training": "Melatih",
+ "fineTune.status.saving": "Menyimpan",
+ "fineTune.status.completed": "Selesai",
+ "fineTune.status.failed": "Gagal",
+ "fineTune.status.stopped": "Dihentikan",
+ "stop.title": "Hentikan pelatihan?",
+ "stop.text": "Checkpoint yang disimpan sekarang memungkinkan Anda melanjutkan atau mengekspor apa yang sudah dipelajari sejauh ini.",
+ "stop.keep": "Hentikan dan simpan checkpoint",
+ "stop.discard": "Hentikan tanpa checkpoint",
+ "stop.cancel": "Lanjutkan pelatihan",
+ "stop.cancelDelete": "Batal",
+ "chart.loss": "Loss",
+ "chart.learning_rate": "Learning rate",
+ "chart.grad_norm": "Norma gradien",
+ "chart.metric": "Metrik",
+ "chart.step": "langkah",
+ "chart.eval": "Loss evaluasi",
+ "chart.waiting": "Menunggu langkah lainnya.",
+ "chart.window": "Langkah {{from}} sampai {{to}} dari {{total}}",
+ "chart.windowEval": "Langkah {{from}} sampai {{to}} dari {{total}}. Titik kosong adalah loss evaluasi.",
+ "chart.aria": "{{metric}} selama langkah pelatihan. Nilai terakhir adalah {{last}} pada langkah {{step}}.",
+ "chart.readout": "langkah {{step}}: {{value}}",
+ "chart.dataTitle": "Tampilkan angkanya",
+ "chart.emptyText": "Grafik digambar setelah job melaporkan dua langkah.",
+ "log.title": "Log",
+ "log.note": "Yang dilaporkan job sejak halaman ini dibuka.",
+ "log.only": "Hanya peringatan dan error",
+ "log.copy": "Salin",
+ "log.copied": "Disalin",
+ "log.download": "Unduh",
+ "log.empty": "Belum ada yang dilaporkan.",
+ "log.noneFiltered": "Tidak ada peringatan atau error.",
+ "checkpoints.title": "Checkpoint",
+ "checkpoints.step": "Langkah",
+ "checkpoints.epoch": "Epoch",
+ "checkpoints.loss": "Loss",
+ "checkpoints.path": "Path",
+ "checkpoints.actions": "Aksi",
+ "checkpoints.resume": "Lanjutkan",
+ "checkpoints.resumeTitle": "Lanjutkan pelatihan dari checkpoint ini",
+ "checkpoints.export": "Ekspor",
+ "checkpoints.exportTitle": "Ekspor checkpoint ini",
+ "export.blocked": "Ekspor dimatikan di server ini karena server tidak berbagi folder model dengan host add-on.",
+ "export.title": "Ekspor sebagai model",
+ "export.lede": "Ekspor mengubah hasilnya menjadi model yang dapat dimuat LocalAI.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Model akhir (direktori output)",
+ "export.checkpointOption": "Langkah {{step}} (loss {{loss}})",
+ "export.format": "Format ekspor",
+ "export.lora": "Adapter LoRA",
+ "export.merged16": "Digabung (16-bit)",
+ "export.merged4": "Digabung (4-bit)",
+ "export.quant": "Kuantisasi",
+ "export.name": "Nama model",
+ "export.nameHint": "Kosongkan untuk membuat nama dari job.",
+ "export.go": "Ekspor",
+ "export.exporting": "Mengekspor...",
+ "export.working": "Ekspor sedang berjalan...",
+ "export.done": "Model diekspor dan didaftarkan sebagai \"{{name}}\"",
+ "export.failed": "Ekspor gagal: {{message}}",
+ "export.unknown": "error tidak diketahui",
+ "export.nextTitle": "{{name}} sekarang adalah sebuah model",
+ "export.nextText": "Model sudah terdaftar di LocalAI. Buka chat dengannya, atau temukan di Model.",
+ "export.chat": "Chat dengan {{name}}",
+ "export.models": "Buka di Model",
+ "export.archive": "Unduh arsip",
+ "jobs.title": "Job sebelumnya",
+ "jobs.count_one": "{{count}} disimpan di server ini",
+ "jobs.count_other": "{{count}} disimpan di server ini",
+ "jobs.loading": "Memuat job",
+ "jobs.emptyTitle": "Belum ada job fine-tuning",
+ "jobs.emptyText": "Siapkan satu di atas. Job akan muncul di sini dengan statusnya, dan Anda dapat menggunakan kembali pengaturannya nanti.",
+ "jobs.model": "Model",
+ "jobs.kind": "Jenis",
+ "jobs.status": "Status",
+ "jobs.started": "Dimulai",
+ "jobs.actions": "Aksi",
+ "jobs.open": "Buka job ini",
+ "jobs.reuse": "Gunakan ulang",
+ "jobs.reuseTitle": "Gunakan pengaturan job ini untuk job baru",
+ "jobs.delete": "Hapus",
+ "jobs.deleteTitle": "Hapus job ini beserta datanya",
+ "jobs.deleteDialogTitle": "Hapus job ini?",
+ "jobs.deleteDialogText": "Ini menghapus job beserta checkpoint dan model hasil ekspor. Tindakan ini tidak dapat dibatalkan.",
+ "off.badge": "Tidak tersedia untuk Anda",
+ "off.fineTune.title": "Akun Anda tidak dapat melakukan fine-tuning",
+ "off.fineTune.text": "Fine-tuning adalah izin tersendiri, dan akun ini tidak memilikinya.",
+ "off.quantize.title": "Akun Anda tidak dapat melakukan kuantisasi",
+ "off.quantize.text": "Kuantisasi adalah izin tersendiri, dan akun ini tidak memilikinya.",
+ "off.admin": "Administrator dapat mengaktifkannya di Pengguna dan kunci.",
+ "off.askAdmin": "Minta administrator untuk mengaktifkannya untuk akun Anda.",
+ "off.back": "Ke Beranda",
+ "landing.experimental": "Eksperimental",
+ "run.unknownPercent": "Kemajuan belum dilaporkan",
+ "export.adminOnly.title": "Mengekspor model hanya untuk administrator",
+ "export.adminOnly.text": "Ekspor menambahkan model ke server ini untuk semua orang yang dapat menggunakannya. Minta administrator untuk mengekspor tugas ini."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.it.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.it.json
new file mode 100644
index 0000000000..042e46491c
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.it.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Avanzamento",
+ "steps.setup": "Configura",
+ "steps.check": "Verifica",
+ "steps.run": "Esecuzione",
+ "steps.result": "Risultato",
+ "run.stop": "Ferma",
+ "run.connecting": "Connessione al job. In attesa del primo evento di avanzamento.",
+ "run.progress": "Avanzamento",
+ "run.stages": "Fasi",
+ "run.step": "passo",
+ "run.of": "di",
+ "run.epoch": "epoca",
+ "run.tokens": "token/s",
+ "run.eta": "circa {{time}} rimanenti",
+ "run.etaSeconds": "circa {{seconds}} s rimanenti",
+ "checks.heading": "Verifica prima di iniziare",
+ "checks.live": "Si aggiorna mentre cambi la configurazione.",
+ "checks.meterLabel": "{{used}} per cento in uso, {{free}} liberi di {{total}}",
+ "checks.openBackends": "Apri Backend",
+ "checks.barOk": "Nulla blocca l'avvio.",
+ "checks.barWarn_one": "{{count}} avviso. Puoi comunque avviare.",
+ "checks.barWarn_other": "{{count}} avvisi. Puoi comunque avviare.",
+ "checks.title.model": "Modello",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memoria",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funzioni di ricompensa",
+ "checks.title.resume": "Ripresa",
+ "checks.title.access": "Accesso al modello",
+ "checks.title.type": "Tipo di quantizzazione",
+ "checks.title.source": "Sorgente",
+ "checks.title.name": "Nome",
+ "checks.fineTune.modelOk": "Addestramento di {{model}}.",
+ "checks.fineTune.modelMissing": "Inserisci il modello da addestrare.",
+ "checks.fineTune.dataOk": "I dati arrivano da {{source}}.",
+ "checks.fineTune.dataMissing": "Aggiungi un dataset: un id Hugging Face o un file caricato.",
+ "checks.fineTune.backendOk": "{{name}} è installato.",
+ "checks.fineTune.backendMissing": "Nessun backend di fine-tuning è installato, quindi il job potrebbe non partire.",
+ "checks.fineTune.backendOther": "{{name}} non è tra i backend di fine-tuning installati ({{list}}).",
+ "checks.fineTune.backendUnknown": "Impossibile leggere i backend installati.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} liberi di {{total}}. Batch size {{batch}}, lunghezza della sequenza {{seq}}, accumulo del gradiente {{accum}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni, e LocalAI non la stima prima dell'avvio del job.",
+ "checks.fineTune.memoryCluster": "{{total}} sul nodo migliore ({{node}}). Batch size {{batch}}, lunghezza della sequenza {{seq}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni.",
+ "checks.fineTune.memoryCpu": "Nessuna GPU trovata. L'addestramento usa la CPU e {{free}} di RAM su {{total}} sono liberi. Aspettati lentezza. Batch size {{batch}}, lunghezza della sequenza {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Questo account non può leggere la memoria della macchina. Batch size {{batch}}, lunghezza della sequenza {{seq}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni.",
+ "checks.fineTune.disk": "{{free}} liberi sul disco dei modelli. Download e checkpoint richiedono spazio.",
+ "checks.fineTune.diskLow": "Solo {{free}} liberi sul disco dei modelli. Download e checkpoint richiedono spazio.",
+ "checks.fineTune.rewardMissing": "GRPO richiede almeno una funzione di ricompensa. Aggiungine una in Altre opzioni.",
+ "checks.fineTune.resume": "L'addestramento continua da {{path}}.",
+ "checks.fineTune.accessNone": "Nessun token impostato. Un modello con accesso limitato richiede un token Hugging Face in Altre opzioni.",
+ "checks.fineTune.accessSet": "È impostato un token Hugging Face.",
+ "checks.quantize.modelOk": "Quantizzazione di {{model}}.",
+ "checks.quantize.modelMissing": "Inserisci il modello da quantizzare.",
+ "checks.quantize.typeMissing": "Inserisci il tipo di quantizzazione personalizzato.",
+ "checks.quantize.backendOk": "{{name}} è installato.",
+ "checks.quantize.backendMissing": "Nessun backend di quantizzazione è installato, quindi il job potrebbe non partire.",
+ "checks.quantize.backendOther": "{{name}} non è tra i backend di quantizzazione installati ({{list}}).",
+ "checks.quantize.backendUnknown": "Impossibile leggere i backend installati.",
+ "checks.quantize.memory": "{{free}} di RAM su {{total}} sono liberi. La conversione gira sulla CPU.",
+ "checks.quantize.disk": "{{free}} liberi sul disco dei modelli. Download, file convertito e risultato vengono scritti su disco.",
+ "checks.quantize.diskLow": "Solo {{free}} liberi sul disco dei modelli. Download, file convertito e risultato vengono scritti su disco.",
+ "checks.quantize.accessNone": "Nessun token impostato. Un modello con accesso limitato richiede un token Hugging Face in Altre opzioni.",
+ "checks.quantize.accessSet": "È impostato un token Hugging Face.",
+ "checks.import.sourceOk_huggingface": "Riconosciuto come repository Hugging Face. LocalAI controlla i suoi file quando importi.",
+ "checks.import.sourceOk_url": "Formato URL riconosciuto. Il contenuto non è stato verificato.",
+ "checks.import.sourceOk_config": "Riconosciuto come file di configurazione del modello. Viene letto e usato così com'è.",
+ "checks.import.sourceOk_oci": "Riconosciuto come riferimento a un'immagine OCI.",
+ "checks.import.sourceOk_ollama": "Riconosciuto come riferimento a un modello Ollama.",
+ "checks.import.sourceOk_local": "Riconosciuto come percorso sulla macchina che esegue LocalAI.",
+ "checks.import.sourceOk": "La sorgente è riconosciuta.",
+ "checks.import.sourceUnknown": "Non sembra un formato noto a LocalAI. Ci proverà comunque, e potrebbe non riuscire.",
+ "checks.import.backendDownload": "{{name}} non è ancora installato. LocalAI lo scarica prima.",
+ "checks.import.backendChosen": "{{name}} è scelto e installato.",
+ "checks.import.backendAuto": "LocalAI sceglie il backend dai file della sorgente quando l'importazione inizia.",
+ "checks.import.nameTaken": "Un modello chiamato {{name}} è già installato.",
+ "checks.import.nameFree": "{{name}} non è in uso.",
+ "checks.import.nameAuto": "Il modello prende il nome dalla sorgente.",
+ "checks.import.disk": "{{free}} liberi sul disco dei modelli. La dimensione del download è nota quando l'importazione inizia.",
+ "checks.import.memory_gpu": "{{free}} di memoria GPU sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory_ram": "{{free}} di RAM sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory_cluster": "{{free}} sono disponibili sul nodo migliore. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory": "{{free}} di memoria sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "form.key": "Chiave",
+ "form.value": "Valore",
+ "form.addOption": "Aggiungi opzione",
+ "form.copy": "Copia negli appunti",
+ "form.more": "Altre opzioni",
+ "form.moreHint": "metodo, backend, adattatore, ottimizzatore, valutazione, funzioni di ricompensa",
+ "form.baseModel": "Modello base",
+ "form.model": "Modello",
+ "form.modelHint": "Un id Hugging Face, o un percorso su questo host.",
+ "form.data": "I tuoi dati",
+ "form.dataset": "Sorgente del dataset",
+ "form.datasetHint": "Un id di dataset Hugging Face, oppure carica un file.",
+ "form.split": "Suddivisione",
+ "form.upload": "Carica invece un file",
+ "form.uploadHint": ".json, .jsonl o .csv",
+ "form.removeFile": "Rimuovi il file",
+ "form.train": "Cosa addestrare",
+ "form.kind": "Tipo di addestramento",
+ "form.kinds.adapter.title": "Adattatore",
+ "form.kinds.adapter.text": "Addestra un piccolo adattatore e lascia invariati i pesi base. È la scelta abituale, e richiede poca memoria.",
+ "form.kinds.full.title": "Modello completo",
+ "form.kinds.full.text": "Cambia ogni peso. Richiede diverse volte la memoria di un adattatore.",
+ "form.epochs": "Epoche",
+ "form.batch": "Batch size",
+ "form.lr": "Tasso di apprendimento",
+ "form.backend": "Backend",
+ "form.method": "Metodo di addestramento",
+ "form.adapterKind": "Tipo di adattatore",
+ "form.rank": "Rank",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Passi di accumulo del gradiente",
+ "form.warmup": "Passi di warmup",
+ "form.seqLen": "Lunghezza massima della sequenza",
+ "form.optimizer": "Ottimizzatore",
+ "form.maxSteps": "Passi massimi",
+ "form.maxStepsHint": "0 per automatico.",
+ "form.saveSteps": "Passi di salvataggio",
+ "form.saveLimit": "Limite totale di salvataggi",
+ "form.saveLimitHint": "0 mantiene ogni checkpoint.",
+ "form.weightDecay": "Weight decay",
+ "form.seed": "Seed",
+ "form.seedHint": "0 sceglie un seed casuale.",
+ "form.precision": "Precisione mista",
+ "form.auto": "Automatico",
+ "form.targetModules": "Moduli target",
+ "form.targetModulesHint": "Separati da virgola. Lascia vuoto per il valore predefinito del backend.",
+ "form.gradCheckpointing": "Gradient checkpointing (più lento, usa meno memoria)",
+ "form.token": "Token Hugging Face",
+ "form.tokenHint": "Serve solo per i modelli con accesso limitato.",
+ "form.evalEnable": "Valuta durante l'addestramento",
+ "form.evalStrategy": "Strategia di valutazione",
+ "form.evalSteps_": "Passi",
+ "form.evalEpoch": "Epoca",
+ "form.evalSteps": "Passi di valutazione",
+ "form.evalStepsHint": "0 coincide con i passi di salvataggio.",
+ "form.evalSplit": "Suddivisione per la valutazione",
+ "form.evalDataset": "Dataset di valutazione",
+ "form.evalDatasetPlaceholder": "Un dataset separato",
+ "form.evalRatio": "Rapporto di suddivisione automatica",
+ "form.rewards": "Funzioni di ricompensa (GRPO)",
+ "form.rewardsHint": "GRPO richiede almeno una funzione di ricompensa. Scegline una integrata o aggiungi la tua.",
+ "form.resume": "Riprendi dal checkpoint",
+ "form.clearCheckpoint": "Cancella il checkpoint",
+ "form.extra": "Opzioni extra",
+ "form.extraHint": "Coppie chiave e valore specifiche del backend.",
+ "form.summaryFull": "modello completo",
+ "form.summary_lora": "adattatore LoRA",
+ "form.summary_loha": "adattatore LoHa",
+ "form.summary_lokr": "adattatore LoKr",
+ "form.recipe": "Addestra un {{kind}} di {{model}} con {{method}}, {{epochs}} epoche sui dati {{data}}.",
+ "form.recipeEmpty": "Scegli un modello e un dataset per vedere cosa verrà addestrato.",
+ "rewards.format_reward": "Verifica il formato ... seguito dalla risposta.",
+ "rewards.reasoning_accuracy_reward": "Confronta il contenuto di con la colonna delle risposte del dataset.",
+ "rewards.length_reward": "Valuta quanto la risposta è vicina a una lunghezza target.",
+ "rewards.xml_tag_reward": "Valuta i tag XML aperti e chiusi correttamente.",
+ "rewards.no_repetition_reward": "Penalizza gli n-gram ripetuti.",
+ "rewards.code_execution_reward": "Verifica che i blocchi di codice Python abbiano una sintassi valida.",
+ "rewards.param_target_length": "Lunghezza target",
+ "rewards.fnName": "Nome della funzione",
+ "rewards.fnBody": "Corpo della funzione",
+ "rewards.fnHint": "Riceve completions e kwargs e deve restituire una lista di float. Sono disponibili re, math, json e string.",
+ "rewards.add": "Aggiungi",
+ "rewards.cancel": "Annulla",
+ "rewards.custom": "Aggiungi una funzione di ricompensa personalizzata",
+ "fineTune.title": "Fine-tuning",
+ "fineTune.lede": "Addestra un adattatore sui tuoi dati, poi esportalo come modello con cui puoi chattare.",
+ "fineTune.importConfig": "Importa configurazione",
+ "fineTune.exportConfig": "Esporta configurazione",
+ "fineTune.newJob": "Nuovo job",
+ "fineTune.badConfig": "Impossibile leggere il file. Usa una configurazione JSON esportata da questa pagina.",
+ "fineTune.start": "Avvia il fine-tuning",
+ "fineTune.starting": "Avvio in corso...",
+ "fineTune.resume": "Riprendi l'addestramento",
+ "fineTune.resuming": "Ripresa dal checkpoint:",
+ "fineTune.clear": "Cancella",
+ "fineTune.barNote": "Non cambia nulla finché non avvii.",
+ "fineTune.jobMeta": "{{kind}} {{method}} su {{backend}}, avviato {{started}}",
+ "fineTune.failed": "Addestramento non riuscito",
+ "fineTune.failedAt": "Addestramento fermato al passo {{step}} di {{total}}",
+ "fineTune.noMessage": "Il backend non ha dato alcun messaggio.",
+ "fineTune.finished": "Addestramento completato.",
+ "fineTune.finishedLoss": "Addestramento completato. L'ultima loss è stata {{loss}}.",
+ "fineTune.stopped": "Il job è stato fermato. Un checkpoint viene conservato se hai scelto di farlo.",
+ "fineTune.tryTitle": "Cosa fare dopo",
+ "fineTune.tryText": "Il server ha segnalato che la memoria è esaurita. Ogni modifica qui sotto viene applicata a una copia della configurazione, e nulla parte finché non premi Avvia.",
+ "fineTune.tryPlain": "Apri di nuovo la configurazione, cambia ciò che indica il messaggio e avvia un nuovo job.",
+ "fineTune.fixBatch": "Riduci il batch size a 1",
+ "fineTune.fixCheckpointing": "Attiva il gradient checkpointing (più lento, usa meno memoria)",
+ "fineTune.retryChanges": "Apri la configurazione con queste modifiche",
+ "fineTune.reuse": "Apri di nuovo la configurazione",
+ "fineTune.resumeFrom": "Riprendi dal passo {{step}}",
+ "fineTune.status.queued": "In coda",
+ "fineTune.status.loading_model": "Caricamento del modello",
+ "fineTune.status.loading_dataset": "Caricamento del dataset",
+ "fineTune.status.training": "Addestramento",
+ "fineTune.status.saving": "Salvataggio",
+ "fineTune.status.completed": "Completato",
+ "fineTune.status.failed": "Non riuscito",
+ "fineTune.status.stopped": "Fermato",
+ "stop.title": "Fermare l'addestramento?",
+ "stop.text": "Un checkpoint salvato ora ti permette di riprendere o esportare ciò che è stato appreso finora.",
+ "stop.keep": "Ferma e conserva un checkpoint",
+ "stop.discard": "Ferma senza checkpoint",
+ "stop.cancel": "Continua l'addestramento",
+ "stop.cancelDelete": "Annulla",
+ "chart.loss": "Loss",
+ "chart.learning_rate": "Tasso di apprendimento",
+ "chart.grad_norm": "Norma del gradiente",
+ "chart.metric": "Metrica",
+ "chart.step": "passo",
+ "chart.eval": "Loss di valutazione",
+ "chart.waiting": "In attesa di altri passi.",
+ "chart.window": "Passi da {{from}} a {{to}} di {{total}}",
+ "chart.windowEval": "Passi da {{from}} a {{to}} di {{total}}. I punti vuoti sono la loss di valutazione.",
+ "chart.aria": "{{metric}} lungo i passi di addestramento. L'ultimo valore è {{last}} al passo {{step}}.",
+ "chart.readout": "passo {{step}}: {{value}}",
+ "chart.dataTitle": "Mostra i numeri",
+ "chart.emptyText": "Il grafico si disegna quando il job ha riportato due passi.",
+ "log.title": "Log",
+ "log.note": "Ciò che il job ha riportato da quando è aperta questa pagina.",
+ "log.only": "Solo avvisi ed errori",
+ "log.copy": "Copia",
+ "log.copied": "Copiato",
+ "log.download": "Scarica",
+ "log.empty": "Ancora nulla da riportare.",
+ "log.noneFiltered": "Nessun avviso o errore.",
+ "checkpoints.title": "Checkpoint",
+ "checkpoints.step": "Passo",
+ "checkpoints.epoch": "Epoca",
+ "checkpoints.loss": "Loss",
+ "checkpoints.path": "Percorso",
+ "checkpoints.actions": "Azioni",
+ "checkpoints.resume": "Riprendi",
+ "checkpoints.resumeTitle": "Riprendi l'addestramento da questo checkpoint",
+ "checkpoints.export": "Esporta",
+ "checkpoints.exportTitle": "Esporta questo checkpoint",
+ "export.blocked": "L'esportazione è disattivata su questo server perché non condivide la cartella dei modelli con l'host dell'add-on.",
+ "export.title": "Esporta come modello",
+ "export.lede": "L'esportazione trasforma il risultato in un modello che LocalAI può caricare.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Modello finale (cartella di output)",
+ "export.checkpointOption": "Passo {{step}} (loss {{loss}})",
+ "export.format": "Formato di esportazione",
+ "export.lora": "Adattatore LoRA",
+ "export.merged16": "Unito (16 bit)",
+ "export.merged4": "Unito (4 bit)",
+ "export.quant": "Quantizzazione",
+ "export.name": "Nome del modello",
+ "export.nameHint": "Lascia vuoto per generarlo dal job.",
+ "export.go": "Esporta",
+ "export.exporting": "Esportazione...",
+ "export.working": "Esportazione in corso...",
+ "export.done": "Modello esportato e registrato come \"{{name}}\"",
+ "export.failed": "Esportazione non riuscita: {{message}}",
+ "export.unknown": "errore sconosciuto",
+ "export.nextTitle": "{{name}} è ora un modello",
+ "export.nextText": "È registrato in LocalAI. Apri una chat con esso, oppure trovalo in Modelli.",
+ "export.chat": "Chatta con {{name}}",
+ "export.models": "Apri in Modelli",
+ "export.archive": "Scarica l'archivio",
+ "jobs.title": "Job precedenti",
+ "jobs.count_one": "{{count}} conservato su questo server",
+ "jobs.count_other": "{{count}} conservati su questo server",
+ "jobs.loading": "Caricamento dei job",
+ "jobs.emptyTitle": "Ancora nessun job di fine-tuning",
+ "jobs.emptyText": "Configurane uno sopra. Compare qui con il suo stato, e in seguito puoi riusarne la configurazione.",
+ "jobs.model": "Modello",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Stato",
+ "jobs.started": "Avviato",
+ "jobs.actions": "Azioni",
+ "jobs.open": "Apri questo job",
+ "jobs.reuse": "Riusa",
+ "jobs.reuseTitle": "Usa la configurazione di questo job per un nuovo job",
+ "jobs.delete": "Elimina",
+ "jobs.deleteTitle": "Elimina questo job e i suoi dati",
+ "jobs.deleteDialogTitle": "Eliminare questo job?",
+ "jobs.deleteDialogText": "Questo elimina il job con i suoi checkpoint e l'eventuale modello esportato. Non può essere annullato.",
+ "off.badge": "Non disponibile per te",
+ "off.fineTune.title": "Il tuo account non può fare fine-tuning",
+ "off.fineTune.text": "Il fine-tuning è un permesso separato, e questo account non ce l'ha.",
+ "off.quantize.title": "Il tuo account non può quantizzare",
+ "off.quantize.text": "La quantizzazione è un permesso separato, e questo account non ce l'ha.",
+ "off.admin": "Un amministratore può attivarlo da Utenti e chiavi.",
+ "off.askAdmin": "Chiedi a un amministratore di attivarlo per il tuo account.",
+ "off.back": "Vai alla Home",
+ "landing.experimental": "Sperimentale",
+ "run.unknownPercent": "Avanzamento non ancora comunicato",
+ "export.adminOnly.title": "L'esportazione di un modello è riservata agli amministratori",
+ "export.adminOnly.text": "L'esportazione aggiunge un modello a questo server per tutti quelli che possono usarlo. Chiedi a un amministratore di esportare questo lavoro."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.ko.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.ko.json
new file mode 100644
index 0000000000..18d02e6398
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.ko.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "진행 상황",
+ "steps.setup": "설정",
+ "steps.check": "확인",
+ "steps.run": "실행",
+ "steps.result": "결과",
+ "run.stop": "중지",
+ "run.connecting": "작업에 연결하는 중입니다. 첫 진행 이벤트를 기다리고 있습니다.",
+ "run.progress": "진행률",
+ "run.stages": "단계",
+ "run.step": "스텝",
+ "run.of": "/",
+ "run.epoch": "에포크",
+ "run.tokens": "tokens/s",
+ "run.eta": "약 {{time}} 남음",
+ "run.etaSeconds": "약 {{seconds}}초 남음",
+ "checks.heading": "시작하기 전에 확인",
+ "checks.live": "설정을 바꿀 때마다 업데이트됩니다.",
+ "checks.meterLabel": "{{used}}% 사용 중, {{total}} 중 {{free}} 사용 가능",
+ "checks.openBackends": "백엔드 열기",
+ "checks.barOk": "시작을 막는 항목이 없습니다.",
+ "checks.barWarn_one": "경고 {{count}}개. 그래도 시작할 수 있습니다.",
+ "checks.barWarn_other": "경고 {{count}}개. 그래도 시작할 수 있습니다.",
+ "checks.title.model": "모델",
+ "checks.title.data": "데이터셋",
+ "checks.title.backend": "백엔드",
+ "checks.title.memory": "메모리",
+ "checks.title.disk": "디스크",
+ "checks.title.reward": "보상 함수",
+ "checks.title.resume": "재개",
+ "checks.title.access": "모델 접근",
+ "checks.title.type": "양자화 유형",
+ "checks.title.source": "소스",
+ "checks.title.name": "이름",
+ "checks.fineTune.modelOk": "{{model}} 학습 중입니다.",
+ "checks.fineTune.modelMissing": "학습할 모델을 입력하세요.",
+ "checks.fineTune.dataOk": "데이터는 {{source}}에서 옵니다.",
+ "checks.fineTune.dataMissing": "데이터셋을 추가하세요: Hugging Face id 또는 업로드한 파일.",
+ "checks.fineTune.backendOk": "{{name}}이(가) 설치되어 있습니다.",
+ "checks.fineTune.backendMissing": "파인튜닝 백엔드가 설치되어 있지 않아 작업이 시작되지 않을 수 있습니다.",
+ "checks.fineTune.backendOther": "{{name}}은(는) 설치된 파인튜닝 백엔드({{list}}) 중에 없습니다.",
+ "checks.fineTune.backendUnknown": "설치된 백엔드를 읽을 수 없습니다.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{total}} 중 {{free}} 사용 가능. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}, 그래디언트 누적 {{accum}}. 작업에 필요한 양은 모델과 이 설정에 따라 다르며, LocalAI는 작업 시작 전에 이를 추정하지 않습니다.",
+ "checks.fineTune.memoryCluster": "최적 노드({{node}})의 {{total}}. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}. 작업에 필요한 양은 모델과 이 설정에 따라 다릅니다.",
+ "checks.fineTune.memoryCpu": "GPU를 찾을 수 없습니다. 학습은 CPU를 사용하며 RAM {{total}} 중 {{free}}가 사용 가능합니다. 느릴 것으로 예상됩니다. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}.",
+ "checks.fineTune.memoryUnknown": "이 계정은 컴퓨터의 메모리를 읽을 수 없습니다. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}. 작업에 필요한 양은 모델과 이 설정에 따라 다릅니다.",
+ "checks.fineTune.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드와 체크포인트에 공간이 필요합니다.",
+ "checks.fineTune.diskLow": "모델 디스크에 {{free}}만 사용 가능. 다운로드와 체크포인트에 공간이 필요합니다.",
+ "checks.fineTune.rewardMissing": "GRPO에는 보상 함수가 최소 하나 필요합니다. 추가 옵션에서 하나를 추가하세요.",
+ "checks.fineTune.resume": "학습이 {{path}}에서 이어집니다.",
+ "checks.fineTune.accessNone": "토큰이 설정되지 않았습니다. 접근이 제한된 모델은 추가 옵션에서 Hugging Face 토큰이 필요합니다.",
+ "checks.fineTune.accessSet": "Hugging Face 토큰이 설정되어 있습니다.",
+ "checks.quantize.modelOk": "{{model}}을(를) 양자화합니다.",
+ "checks.quantize.modelMissing": "양자화할 모델을 입력하세요.",
+ "checks.quantize.typeMissing": "사용자 지정 양자화 유형을 입력하세요.",
+ "checks.quantize.backendOk": "{{name}}이(가) 설치되어 있습니다.",
+ "checks.quantize.backendMissing": "양자화 백엔드가 설치되어 있지 않아 작업이 시작되지 않을 수 있습니다.",
+ "checks.quantize.backendOther": "{{name}}은(는) 설치된 양자화 백엔드({{list}}) 중에 없습니다.",
+ "checks.quantize.backendUnknown": "설치된 백엔드를 읽을 수 없습니다.",
+ "checks.quantize.memory": "RAM {{total}} 중 {{free}}가 사용 가능합니다. 변환은 CPU에서 실행됩니다.",
+ "checks.quantize.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드, 변환된 파일, 결과가 디스크에 기록됩니다.",
+ "checks.quantize.diskLow": "모델 디스크에 {{free}}만 사용 가능. 다운로드, 변환된 파일, 결과가 디스크에 기록됩니다.",
+ "checks.quantize.accessNone": "토큰이 설정되지 않았습니다. 접근이 제한된 모델은 추가 옵션에서 Hugging Face 토큰이 필요합니다.",
+ "checks.quantize.accessSet": "Hugging Face 토큰이 설정되어 있습니다.",
+ "checks.import.sourceOk_huggingface": "Hugging Face 저장소로 인식되었습니다. 가져올 때 LocalAI가 파일을 확인합니다.",
+ "checks.import.sourceOk_url": "URL 형식이 인식되었습니다. 내용은 확인하지 않았습니다.",
+ "checks.import.sourceOk_config": "모델 구성 파일로 인식되었습니다. 읽은 뒤 있는 그대로 사용합니다.",
+ "checks.import.sourceOk_oci": "OCI 이미지 참조로 인식되었습니다.",
+ "checks.import.sourceOk_ollama": "Ollama 모델 참조로 인식되었습니다.",
+ "checks.import.sourceOk_local": "LocalAI를 실행하는 컴퓨터의 경로로 인식되었습니다.",
+ "checks.import.sourceOk": "소스가 인식되었습니다.",
+ "checks.import.sourceUnknown": "LocalAI가 아는 형식이 아닌 것 같습니다. 그래도 시도하며, 실패할 수 있습니다.",
+ "checks.import.backendDownload": "{{name}}이(가) 아직 설치되어 있지 않습니다. LocalAI가 먼저 다운로드합니다.",
+ "checks.import.backendChosen": "{{name}}이(가) 선택되어 설치되어 있습니다.",
+ "checks.import.backendAuto": "가져오기가 시작되면 LocalAI가 소스의 파일을 보고 백엔드를 고릅니다.",
+ "checks.import.nameTaken": "{{name}}이라는 모델이 이미 설치되어 있습니다.",
+ "checks.import.nameFree": "{{name}}은(는) 사용 중이 아닙니다.",
+ "checks.import.nameAuto": "모델은 소스 이름을 따라 명명됩니다.",
+ "checks.import.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드 크기는 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_gpu": "지금 GPU 메모리 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_ram": "지금 RAM {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_cluster": "최적 노드에 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory": "지금 메모리 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "form.key": "키",
+ "form.value": "값",
+ "form.addOption": "옵션 추가",
+ "form.copy": "클립보드에 복사",
+ "form.more": "추가 옵션",
+ "form.moreHint": "방법, 백엔드, 어댑터, 옵티마이저, 평가, 보상 함수",
+ "form.baseModel": "기본 모델",
+ "form.model": "모델",
+ "form.modelHint": "Hugging Face id 또는 이 호스트의 경로.",
+ "form.data": "내 데이터",
+ "form.dataset": "데이터셋 소스",
+ "form.datasetHint": "Hugging Face 데이터셋 id 또는 파일 업로드.",
+ "form.split": "분할",
+ "form.upload": "대신 파일 업로드",
+ "form.uploadHint": ".json, .jsonl 또는 .csv",
+ "form.removeFile": "파일 제거",
+ "form.train": "학습 대상",
+ "form.kind": "학습 종류",
+ "form.kinds.adapter.title": "어댑터",
+ "form.kinds.adapter.text": "작은 어댑터를 학습하고 기본 가중치는 건드리지 않습니다. 보통 이 방식을 선택하며 메모리가 적게 듭니다.",
+ "form.kinds.full.title": "전체 모델",
+ "form.kinds.full.text": "모든 가중치를 변경합니다. 어댑터보다 여러 배의 메모리가 필요합니다.",
+ "form.epochs": "에포크",
+ "form.batch": "배치 크기",
+ "form.lr": "학습률",
+ "form.backend": "백엔드",
+ "form.method": "학습 방법",
+ "form.adapterKind": "어댑터 종류",
+ "form.rank": "랭크",
+ "form.alpha": "알파",
+ "form.dropout": "드롭아웃",
+ "form.gradAccum": "그래디언트 누적 스텝",
+ "form.warmup": "워밍업 스텝",
+ "form.seqLen": "최대 시퀀스 길이",
+ "form.optimizer": "옵티마이저",
+ "form.maxSteps": "최대 스텝",
+ "form.maxStepsHint": "0이면 자동입니다.",
+ "form.saveSteps": "저장 스텝",
+ "form.saveLimit": "총 저장 한도",
+ "form.saveLimitHint": "0이면 모든 체크포인트를 유지합니다.",
+ "form.weightDecay": "가중치 감쇠",
+ "form.seed": "시드",
+ "form.seedHint": "0이면 무작위 시드를 고릅니다.",
+ "form.precision": "혼합 정밀도",
+ "form.auto": "자동",
+ "form.targetModules": "대상 모듈",
+ "form.targetModulesHint": "쉼표로 구분합니다. 비워 두면 백엔드 기본값을 사용합니다.",
+ "form.gradCheckpointing": "그래디언트 체크포인팅 (느리지만 메모리를 적게 사용)",
+ "form.token": "Hugging Face 토큰",
+ "form.tokenHint": "접근이 제한된 모델에만 필요합니다.",
+ "form.evalEnable": "학습 중 평가",
+ "form.evalStrategy": "평가 전략",
+ "form.evalSteps_": "스텝",
+ "form.evalEpoch": "에포크",
+ "form.evalSteps": "평가 스텝",
+ "form.evalStepsHint": "0이면 저장 스텝과 같게 설정됩니다.",
+ "form.evalSplit": "평가 분할",
+ "form.evalDataset": "평가 데이터셋",
+ "form.evalDatasetPlaceholder": "별도 데이터셋",
+ "form.evalRatio": "자동 분할 비율",
+ "form.rewards": "보상 함수 (GRPO)",
+ "form.rewardsHint": "GRPO에는 보상 함수가 최소 하나 필요합니다. 기본 제공 함수를 고르거나 직접 추가하세요.",
+ "form.resume": "체크포인트에서 재개",
+ "form.clearCheckpoint": "체크포인트 지우기",
+ "form.extra": "추가 옵션",
+ "form.extraHint": "백엔드별 키와 값 쌍입니다.",
+ "form.summaryFull": "전체 모델",
+ "form.summary_lora": "LoRA 어댑터",
+ "form.summary_loha": "LoHa 어댑터",
+ "form.summary_lokr": "LoKr 어댑터",
+ "form.recipe": "{{data}}를 사용해 {{method}}로 {{model}}의 {{kind}}를 {{epochs}} 에포크 학습합니다.",
+ "form.recipeEmpty": "모델과 데이터셋을 선택하면 학습 내용을 볼 수 있습니다.",
+ "rewards.format_reward": "... 형식과 그 다음 답변 형식을 확인합니다.",
+ "rewards.reasoning_accuracy_reward": " 내용을 데이터셋의 답변 열과 비교합니다.",
+ "rewards.length_reward": "답변이 목표 길이에 얼마나 가까운지 점수를 매깁니다.",
+ "rewards.xml_tag_reward": "열고 닫은 XML 태그가 올바른지 점수를 매깁니다.",
+ "rewards.no_repetition_reward": "반복되는 n-gram에 감점을 줍니다.",
+ "rewards.code_execution_reward": "Python 코드 블록의 문법이 유효한지 확인합니다.",
+ "rewards.param_target_length": "목표 길이",
+ "rewards.fnName": "함수 이름",
+ "rewards.fnBody": "함수 본문",
+ "rewards.fnHint": "completions와 kwargs를 받아 float 리스트를 반환해야 합니다. re, math, json, string을 사용할 수 있습니다.",
+ "rewards.add": "추가",
+ "rewards.cancel": "취소",
+ "rewards.custom": "사용자 지정 보상 함수 추가",
+ "fineTune.title": "파인튜닝",
+ "fineTune.lede": "내 데이터로 어댑터를 학습한 뒤, 대화할 수 있는 모델로 내보냅니다.",
+ "fineTune.importConfig": "구성 가져오기",
+ "fineTune.exportConfig": "구성 내보내기",
+ "fineTune.newJob": "새 작업",
+ "fineTune.badConfig": "파일을 읽을 수 없습니다. 이 페이지에서 내보낸 JSON 구성을 사용하세요.",
+ "fineTune.start": "파인튜닝 시작",
+ "fineTune.starting": "시작 중...",
+ "fineTune.resume": "학습 재개",
+ "fineTune.resuming": "체크포인트에서 재개 중:",
+ "fineTune.clear": "지우기",
+ "fineTune.barNote": "시작하기 전까지는 아무것도 바뀌지 않습니다.",
+ "fineTune.jobMeta": "{{backend}}의 {{kind}} {{method}}, 시작 {{started}}",
+ "fineTune.failed": "학습 실패",
+ "fineTune.failedAt": "{{total}} 스텝 중 {{step}} 스텝에서 학습이 중지되었습니다",
+ "fineTune.noMessage": "백엔드가 메시지를 보내지 않았습니다.",
+ "fineTune.finished": "학습이 완료되었습니다.",
+ "fineTune.finishedLoss": "학습이 완료되었습니다. 마지막 손실은 {{loss}}입니다.",
+ "fineTune.stopped": "작업이 중지되었습니다. 유지하도록 선택한 경우 체크포인트가 보존됩니다.",
+ "fineTune.tryTitle": "다음에 할 일",
+ "fineTune.tryText": "서버가 메모리 부족을 보고했습니다. 아래의 각 변경 사항은 설정 사본에 적용되며, 시작을 누르기 전까지 아무것도 실행되지 않습니다.",
+ "fineTune.tryPlain": "설정을 다시 열고, 메시지가 가리키는 항목을 바꾼 뒤 새 작업을 시작하세요.",
+ "fineTune.fixBatch": "배치 크기를 1로 낮추기",
+ "fineTune.fixCheckpointing": "그래디언트 체크포인팅 켜기 (느리지만 메모리를 적게 사용)",
+ "fineTune.retryChanges": "이 변경 사항을 적용해 설정 열기",
+ "fineTune.reuse": "설정 다시 열기",
+ "fineTune.resumeFrom": "{{step}} 스텝부터 재개",
+ "fineTune.status.queued": "대기 중",
+ "fineTune.status.loading_model": "모델 불러오는 중",
+ "fineTune.status.loading_dataset": "데이터셋 불러오는 중",
+ "fineTune.status.training": "학습 중",
+ "fineTune.status.saving": "저장 중",
+ "fineTune.status.completed": "완료됨",
+ "fineTune.status.failed": "실패",
+ "fineTune.status.stopped": "중지됨",
+ "stop.title": "학습을 중지할까요?",
+ "stop.text": "지금 저장된 체크포인트가 있으면 지금까지 학습된 내용을 재개하거나 내보낼 수 있습니다.",
+ "stop.keep": "중지하고 체크포인트 유지",
+ "stop.discard": "체크포인트 없이 중지",
+ "stop.cancel": "학습 계속하기",
+ "stop.cancelDelete": "취소",
+ "chart.loss": "손실",
+ "chart.learning_rate": "학습률",
+ "chart.grad_norm": "그래디언트 노름",
+ "chart.metric": "지표",
+ "chart.step": "스텝",
+ "chart.eval": "평가 손실",
+ "chart.waiting": "더 많은 스텝을 기다리는 중입니다.",
+ "chart.window": "{{total}} 중 {{from}}~{{to}} 스텝",
+ "chart.windowEval": "{{total}} 중 {{from}}~{{to}} 스텝. 빈 점은 평가 손실입니다.",
+ "chart.aria": "학습 스텝에 따른 {{metric}}. 마지막 값은 {{step}} 스텝의 {{last}}입니다.",
+ "chart.readout": "{{step}} 스텝: {{value}}",
+ "chart.dataTitle": "숫자 보기",
+ "chart.emptyText": "작업이 두 스텝을 보고하면 차트가 그려집니다.",
+ "log.title": "로그",
+ "log.note": "이 페이지가 열린 뒤 작업이 보고한 내용입니다.",
+ "log.only": "경고와 오류만",
+ "log.copy": "복사",
+ "log.copied": "복사됨",
+ "log.download": "다운로드",
+ "log.empty": "아직 보고된 내용이 없습니다.",
+ "log.noneFiltered": "경고나 오류가 없습니다.",
+ "checkpoints.title": "체크포인트",
+ "checkpoints.step": "스텝",
+ "checkpoints.epoch": "에포크",
+ "checkpoints.loss": "손실",
+ "checkpoints.path": "경로",
+ "checkpoints.actions": "작업",
+ "checkpoints.resume": "재개",
+ "checkpoints.resumeTitle": "이 체크포인트에서 학습 재개",
+ "checkpoints.export": "내보내기",
+ "checkpoints.exportTitle": "이 체크포인트 내보내기",
+ "export.blocked": "이 서버는 애드온 호스트와 모델 폴더를 공유하지 않으므로 내보내기가 꺼져 있습니다.",
+ "export.title": "모델로 내보내기",
+ "export.lede": "내보내기는 결과를 LocalAI가 불러올 수 있는 모델로 바꿉니다.",
+ "export.checkpoint": "체크포인트",
+ "export.final": "최종 모델 (출력 디렉터리)",
+ "export.checkpointOption": "{{step}} 스텝 (손실 {{loss}})",
+ "export.format": "내보내기 형식",
+ "export.lora": "LoRA 어댑터",
+ "export.merged16": "병합 (16비트)",
+ "export.merged4": "병합 (4비트)",
+ "export.quant": "양자화",
+ "export.name": "모델 이름",
+ "export.nameHint": "비워 두면 작업으로부터 이름을 생성합니다.",
+ "export.go": "내보내기",
+ "export.exporting": "내보내는 중...",
+ "export.working": "내보내기 진행 중...",
+ "export.done": "모델을 내보냈고 \"{{name}}\"(으)로 등록했습니다",
+ "export.failed": "내보내기 실패: {{message}}",
+ "export.unknown": "알 수 없는 오류",
+ "export.nextTitle": "{{name}}이(가) 이제 모델입니다",
+ "export.nextText": "LocalAI에 등록되었습니다. 이 모델과 채팅을 열거나 모델 페이지에서 찾으세요.",
+ "export.chat": "{{name}}과(와) 채팅",
+ "export.models": "모델에서 열기",
+ "export.archive": "아카이브 다운로드",
+ "jobs.title": "이전 작업",
+ "jobs.count_one": "이 서버에 {{count}}개 보관됨",
+ "jobs.count_other": "이 서버에 {{count}}개 보관됨",
+ "jobs.loading": "작업 불러오는 중",
+ "jobs.emptyTitle": "아직 파인튜닝 작업이 없습니다",
+ "jobs.emptyText": "위에서 작업을 설정하세요. 작업은 상태와 함께 여기에 나타나며, 나중에 설정을 재사용할 수 있습니다.",
+ "jobs.model": "모델",
+ "jobs.kind": "종류",
+ "jobs.status": "상태",
+ "jobs.started": "시작 시간",
+ "jobs.actions": "작업",
+ "jobs.open": "이 작업 열기",
+ "jobs.reuse": "재사용",
+ "jobs.reuseTitle": "이 작업의 설정을 새 작업에 사용",
+ "jobs.delete": "삭제",
+ "jobs.deleteTitle": "이 작업과 데이터 삭제",
+ "jobs.deleteDialogTitle": "이 작업을 삭제할까요?",
+ "jobs.deleteDialogText": "작업을 체크포인트와 내보낸 모델과 함께 삭제합니다. 이 작업은 되돌릴 수 없습니다.",
+ "off.badge": "사용할 수 없음",
+ "off.fineTune.title": "이 계정은 파인튜닝을 할 수 없습니다",
+ "off.fineTune.text": "파인튜닝은 별도의 권한이며, 이 계정에는 그 권한이 없습니다.",
+ "off.quantize.title": "이 계정은 양자화를 할 수 없습니다",
+ "off.quantize.text": "양자화는 별도의 권한이며, 이 계정에는 그 권한이 없습니다.",
+ "off.admin": "관리자가 사용자 및 키 메뉴에서 켤 수 있습니다.",
+ "off.askAdmin": "관리자에게 이 계정에 대해 켜 달라고 요청하세요.",
+ "off.back": "홈으로 이동",
+ "landing.experimental": "실험적",
+ "run.unknownPercent": "진행률이 아직 보고되지 않았습니다",
+ "export.adminOnly.title": "모델 내보내기는 관리자 전용입니다",
+ "export.adminOnly.text": "내보내기는 이 서버를 쓸 수 있는 모든 사람을 위해 모델을 추가합니다. 관리자에게 이 작업의 내보내기를 요청하세요."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.pt-BR.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.pt-BR.json
new file mode 100644
index 0000000000..b3f1183f52
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.pt-BR.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "Progresso",
+ "steps.setup": "Configurar",
+ "steps.check": "Verificar",
+ "steps.run": "Executar",
+ "steps.result": "Resultado",
+ "run.stop": "Parar",
+ "run.connecting": "Conectando ao job. Aguardando o primeiro evento de progresso.",
+ "run.progress": "Progresso",
+ "run.stages": "Etapas",
+ "run.step": "passo",
+ "run.of": "de",
+ "run.epoch": "época",
+ "run.tokens": "tokens/s",
+ "run.eta": "cerca de {{time}} restantes",
+ "run.etaSeconds": "cerca de {{seconds}} s restantes",
+ "checks.heading": "Verifique antes de começar",
+ "checks.live": "Atualiza conforme você muda a configuração.",
+ "checks.meterLabel": "{{used}} por cento em uso, {{free}} livres de {{total}}",
+ "checks.openBackends": "Abrir Backends",
+ "checks.barOk": "Nada impede o início.",
+ "checks.barWarn_one": "{{count}} aviso. Você ainda pode iniciar.",
+ "checks.barWarn_other": "{{count}} avisos. Você ainda pode iniciar.",
+ "checks.title.model": "Modelo",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memória",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funções de recompensa",
+ "checks.title.resume": "Retomando",
+ "checks.title.access": "Acesso ao modelo",
+ "checks.title.type": "Tipo de quantização",
+ "checks.title.source": "Origem",
+ "checks.title.name": "Nome",
+ "checks.fineTune.modelOk": "Treinando {{model}}.",
+ "checks.fineTune.modelMissing": "Informe o modelo a treinar.",
+ "checks.fineTune.dataOk": "Os dados vêm de {{source}}.",
+ "checks.fineTune.dataMissing": "Adicione um dataset: um id do Hugging Face ou um arquivo enviado.",
+ "checks.fineTune.backendOk": "{{name}} está instalado.",
+ "checks.fineTune.backendMissing": "Nenhum backend de ajuste fino está instalado, então o job pode não iniciar.",
+ "checks.fineTune.backendOther": "{{name}} não está entre os backends de ajuste fino instalados ({{list}}).",
+ "checks.fineTune.backendUnknown": "Não foi possível ler os backends instalados.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} de {{total}} livres. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}, acumulação de gradiente {{accum}}. Quanto o job precisa depende do modelo e destas configurações, e o LocalAI não estima isso antes de o job começar.",
+ "checks.fineTune.memoryCluster": "{{total}} no melhor nó ({{node}}). Tamanho do lote {{batch}}, comprimento da sequência {{seq}}. Quanto o job precisa depende do modelo e destas configurações.",
+ "checks.fineTune.memoryCpu": "Nenhuma GPU encontrada. O treino usa a CPU e {{free}} de {{total}} de RAM estão livres. Espere que seja lento. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Esta conta não consegue ler a memória da máquina. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}. Quanto o job precisa depende do modelo e destas configurações.",
+ "checks.fineTune.disk": "{{free}} livres no disco de modelos. Downloads e checkpoints precisam de espaço.",
+ "checks.fineTune.diskLow": "Só {{free}} livres no disco de modelos. Downloads e checkpoints precisam de espaço.",
+ "checks.fineTune.rewardMissing": "GRPO precisa de pelo menos uma função de recompensa. Adicione uma em Mais opções.",
+ "checks.fineTune.resume": "O treino continua a partir de {{path}}.",
+ "checks.fineTune.accessNone": "Nenhum token definido. Um modelo com acesso restrito precisa de um token do Hugging Face em Mais opções.",
+ "checks.fineTune.accessSet": "Um token do Hugging Face está definido.",
+ "checks.quantize.modelOk": "Quantizando {{model}}.",
+ "checks.quantize.modelMissing": "Informe o modelo a quantizar.",
+ "checks.quantize.typeMissing": "Informe o tipo de quantização personalizado.",
+ "checks.quantize.backendOk": "{{name}} está instalado.",
+ "checks.quantize.backendMissing": "Nenhum backend de quantização está instalado, então o job pode não iniciar.",
+ "checks.quantize.backendOther": "{{name}} não está entre os backends de quantização instalados ({{list}}).",
+ "checks.quantize.backendUnknown": "Não foi possível ler os backends instalados.",
+ "checks.quantize.memory": "{{free}} de {{total}} de RAM livres. A conversão roda na CPU.",
+ "checks.quantize.disk": "{{free}} livres no disco de modelos. O download, o arquivo convertido e o resultado são gravados em disco.",
+ "checks.quantize.diskLow": "Só {{free}} livres no disco de modelos. O download, o arquivo convertido e o resultado são gravados em disco.",
+ "checks.quantize.accessNone": "Nenhum token definido. Um modelo com acesso restrito precisa de um token do Hugging Face em Mais opções.",
+ "checks.quantize.accessSet": "Um token do Hugging Face está definido.",
+ "checks.import.sourceOk_huggingface": "Reconhecido como um repositório do Hugging Face. O LocalAI verifica os arquivos dele ao importar.",
+ "checks.import.sourceOk_url": "Formato de URL reconhecido. O conteúdo não foi verificado.",
+ "checks.import.sourceOk_config": "Reconhecido como um arquivo de configuração de modelo. Ele é lido e usado como está.",
+ "checks.import.sourceOk_oci": "Reconhecido como uma referência de imagem OCI.",
+ "checks.import.sourceOk_ollama": "Reconhecido como uma referência de modelo do Ollama.",
+ "checks.import.sourceOk_local": "Reconhecido como um caminho na máquina que executa o LocalAI.",
+ "checks.import.sourceOk": "A origem foi reconhecida.",
+ "checks.import.sourceUnknown": "Isto não parece com nenhum formato que o LocalAI conheça. Ele vai tentar mesmo assim, e pode falhar.",
+ "checks.import.backendDownload": "{{name}} ainda não está instalado. O LocalAI o baixa primeiro.",
+ "checks.import.backendChosen": "{{name}} foi escolhido e está instalado.",
+ "checks.import.backendAuto": "O LocalAI escolhe o backend pelos arquivos da origem quando a importação começa.",
+ "checks.import.nameTaken": "Já existe um modelo instalado chamado {{name}}.",
+ "checks.import.nameFree": "{{name}} não está em uso.",
+ "checks.import.nameAuto": "O modelo recebe o nome da origem.",
+ "checks.import.disk": "{{free}} livres no disco de modelos. O tamanho do download é conhecido quando a importação começa.",
+ "checks.import.memory_gpu": "{{free}} de memória da GPU estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory_ram": "{{free}} de RAM estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory_cluster": "{{free}} estão disponíveis no melhor nó. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory": "{{free}} de memória estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "form.key": "Chave",
+ "form.value": "Valor",
+ "form.addOption": "Adicionar opção",
+ "form.copy": "Copiar para a área de transferência",
+ "form.more": "Mais opções",
+ "form.moreHint": "método, backend, adaptador, otimizador, avaliação, funções de recompensa",
+ "form.baseModel": "Modelo base",
+ "form.model": "Modelo",
+ "form.modelHint": "Um id do Hugging Face ou um caminho nesta máquina.",
+ "form.data": "Seus dados",
+ "form.dataset": "Origem do dataset",
+ "form.datasetHint": "Um id de dataset do Hugging Face, ou envie um arquivo.",
+ "form.split": "Divisão",
+ "form.upload": "Enviar um arquivo no lugar",
+ "form.uploadHint": ".json, .jsonl ou .csv",
+ "form.removeFile": "Remover o arquivo",
+ "form.train": "O que treinar",
+ "form.kind": "Tipo de treino",
+ "form.kinds.adapter.title": "Adaptador",
+ "form.kinds.adapter.text": "Treina um adaptador pequeno e mantém os pesos base intactos. A escolha usual, e precisa de pouca memória.",
+ "form.kinds.full.title": "Modelo completo",
+ "form.kinds.full.text": "Altera todos os pesos. Precisa de várias vezes a memória de um adaptador.",
+ "form.epochs": "Épocas",
+ "form.batch": "Tamanho do lote",
+ "form.lr": "Taxa de aprendizado",
+ "form.backend": "Backend",
+ "form.method": "Método de treino",
+ "form.adapterKind": "Tipo de adaptador",
+ "form.rank": "Rank",
+ "form.alpha": "Alfa",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "Passos de acumulação de gradiente",
+ "form.warmup": "Passos de aquecimento",
+ "form.seqLen": "Comprimento máximo da sequência",
+ "form.optimizer": "Otimizador",
+ "form.maxSteps": "Máximo de passos",
+ "form.maxStepsHint": "0 para automático.",
+ "form.saveSteps": "Passos para salvar",
+ "form.saveLimit": "Limite total de salvamentos",
+ "form.saveLimitHint": "0 mantém todos os checkpoints.",
+ "form.weightDecay": "Decaimento de peso",
+ "form.seed": "Semente",
+ "form.seedHint": "0 escolhe uma semente aleatória.",
+ "form.precision": "Precisão mista",
+ "form.auto": "Automático",
+ "form.targetModules": "Módulos alvo",
+ "form.targetModulesHint": "Separados por vírgula. Deixe em branco para o padrão do backend.",
+ "form.gradCheckpointing": "Checkpointing de gradiente (mais lento, usa menos memória)",
+ "form.token": "Token do Hugging Face",
+ "form.tokenHint": "Só é necessário para modelos com acesso restrito.",
+ "form.evalEnable": "Avaliar durante o treino",
+ "form.evalStrategy": "Estratégia de avaliação",
+ "form.evalSteps_": "Passos",
+ "form.evalEpoch": "Época",
+ "form.evalSteps": "Passos de avaliação",
+ "form.evalStepsHint": "0 usa os passos para salvar.",
+ "form.evalSplit": "Divisão de avaliação",
+ "form.evalDataset": "Dataset de avaliação",
+ "form.evalDatasetPlaceholder": "Um dataset separado",
+ "form.evalRatio": "Proporção da divisão automática",
+ "form.rewards": "Funções de recompensa (GRPO)",
+ "form.rewardsHint": "GRPO precisa de pelo menos uma função de recompensa. Escolha as integradas ou adicione as suas.",
+ "form.resume": "Retomar do checkpoint",
+ "form.clearCheckpoint": "Limpar o checkpoint",
+ "form.extra": "Opções extras",
+ "form.extraHint": "Pares de chave e valor específicos do backend.",
+ "form.summaryFull": "modelo completo",
+ "form.summary_lora": "adaptador LoRA",
+ "form.summary_loha": "adaptador LoHa",
+ "form.summary_lokr": "adaptador LoKr",
+ "form.recipe": "Treinar um {{kind}} de {{model}} com {{method}}, {{epochs}} épocas sobre {{data}}.",
+ "form.recipeEmpty": "Escolha um modelo e um dataset para ver o que será treinado.",
+ "rewards.format_reward": "Verifica o formato ... seguido da resposta.",
+ "rewards.reasoning_accuracy_reward": "Compara o conteúdo de com a coluna de resposta do dataset.",
+ "rewards.length_reward": "Pontua o quanto a resposta se aproxima de um comprimento alvo.",
+ "rewards.xml_tag_reward": "Pontua tags XML abertas e fechadas corretamente.",
+ "rewards.no_repetition_reward": "Penaliza n-gramas repetidos.",
+ "rewards.code_execution_reward": "Verifica se os blocos de código Python têm sintaxe válida.",
+ "rewards.param_target_length": "Comprimento alvo",
+ "rewards.fnName": "Nome da função",
+ "rewards.fnBody": "Corpo da função",
+ "rewards.fnHint": "Recebe completions e kwargs e deve retornar uma lista de floats. re, math, json e string estão disponíveis.",
+ "rewards.add": "Adicionar",
+ "rewards.cancel": "Cancelar",
+ "rewards.custom": "Adicionar uma função de recompensa personalizada",
+ "fineTune.title": "Ajuste fino",
+ "fineTune.lede": "Treine um adaptador com os seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar.",
+ "fineTune.importConfig": "Importar configuração",
+ "fineTune.exportConfig": "Exportar configuração",
+ "fineTune.newJob": "Novo job",
+ "fineTune.badConfig": "Não foi possível ler o arquivo. Use uma configuração JSON exportada desta página.",
+ "fineTune.start": "Iniciar ajuste fino",
+ "fineTune.starting": "Iniciando...",
+ "fineTune.resume": "Retomar treino",
+ "fineTune.resuming": "Retomando do checkpoint:",
+ "fineTune.clear": "Limpar",
+ "fineTune.barNote": "Nada é alterado até você iniciar.",
+ "fineTune.jobMeta": "{{kind}} {{method}} em {{backend}}, iniciado {{started}}",
+ "fineTune.failed": "O treino falhou",
+ "fineTune.failedAt": "O treino parou no passo {{step}} de {{total}}",
+ "fineTune.noMessage": "O backend não enviou mensagem.",
+ "fineTune.finished": "Treino concluído.",
+ "fineTune.finishedLoss": "Treino concluído. A última perda foi {{loss}}.",
+ "fineTune.stopped": "O job foi interrompido. Um checkpoint é mantido se você escolheu mantê-lo.",
+ "fineTune.tryTitle": "O que fazer a seguir",
+ "fineTune.tryText": "O servidor informou que a memória acabou. Cada alteração abaixo é aplicada a uma cópia da configuração, e nada começa até você pressionar Iniciar.",
+ "fineTune.tryPlain": "Abra a configuração de novo, altere o que a mensagem aponta e inicie um novo job.",
+ "fineTune.fixBatch": "Reduzir o tamanho do lote para 1",
+ "fineTune.fixCheckpointing": "Ativar o checkpointing de gradiente (mais lento, usa menos memória)",
+ "fineTune.retryChanges": "Abrir a configuração com estas alterações",
+ "fineTune.reuse": "Abrir a configuração de novo",
+ "fineTune.resumeFrom": "Retomar do passo {{step}}",
+ "fineTune.status.queued": "Na fila",
+ "fineTune.status.loading_model": "Carregando modelo",
+ "fineTune.status.loading_dataset": "Carregando dataset",
+ "fineTune.status.training": "Treinando",
+ "fineTune.status.saving": "Salvando",
+ "fineTune.status.completed": "Concluído",
+ "fineTune.status.failed": "Falhou",
+ "fineTune.status.stopped": "Parado",
+ "stop.title": "Parar o treino?",
+ "stop.text": "Um checkpoint salvo agora permite retomar ou exportar o que foi aprendido até aqui.",
+ "stop.keep": "Parar e manter um checkpoint",
+ "stop.discard": "Parar sem checkpoint",
+ "stop.cancel": "Continuar treinando",
+ "stop.cancelDelete": "Cancelar",
+ "chart.loss": "Perda",
+ "chart.learning_rate": "Taxa de aprendizado",
+ "chart.grad_norm": "Norma do gradiente",
+ "chart.metric": "Métrica",
+ "chart.step": "passo",
+ "chart.eval": "Perda de avaliação",
+ "chart.waiting": "Aguardando mais passos.",
+ "chart.window": "Passos de {{from}} a {{to}} de {{total}}",
+ "chart.windowEval": "Passos de {{from}} a {{to}} de {{total}}. Os pontos vazios são a perda de avaliação.",
+ "chart.aria": "{{metric}} ao longo dos passos de treino. O último valor é {{last}} no passo {{step}}.",
+ "chart.readout": "passo {{step}}: {{value}}",
+ "chart.dataTitle": "Mostrar os números",
+ "chart.emptyText": "O gráfico é desenhado quando o job informar dois passos.",
+ "log.title": "Log",
+ "log.note": "O que o job informou desde que esta página foi aberta.",
+ "log.only": "Somente avisos e erros",
+ "log.copy": "Copiar",
+ "log.copied": "Copiado",
+ "log.download": "Baixar",
+ "log.empty": "Nada informado ainda.",
+ "log.noneFiltered": "Nenhum aviso ou erro.",
+ "checkpoints.title": "Checkpoints",
+ "checkpoints.step": "Passo",
+ "checkpoints.epoch": "Época",
+ "checkpoints.loss": "Perda",
+ "checkpoints.path": "Caminho",
+ "checkpoints.actions": "Ações",
+ "checkpoints.resume": "Retomar",
+ "checkpoints.resumeTitle": "Retomar o treino a partir deste checkpoint",
+ "checkpoints.export": "Exportar",
+ "checkpoints.exportTitle": "Exportar este checkpoint",
+ "export.blocked": "A exportação está desativada neste servidor porque ele não compartilha a pasta de modelos com o host do complemento.",
+ "export.title": "Exportar como modelo",
+ "export.lede": "Exportar transforma o resultado em um modelo que o LocalAI pode carregar.",
+ "export.checkpoint": "Checkpoint",
+ "export.final": "Modelo final (diretório de saída)",
+ "export.checkpointOption": "Passo {{step}} (perda {{loss}})",
+ "export.format": "Formato de exportação",
+ "export.lora": "Adaptador LoRA",
+ "export.merged16": "Mesclado (16 bits)",
+ "export.merged4": "Mesclado (4 bits)",
+ "export.quant": "Quantização",
+ "export.name": "Nome do modelo",
+ "export.nameHint": "Deixe em branco para gerar um nome a partir do job.",
+ "export.go": "Exportar",
+ "export.exporting": "Exportando...",
+ "export.working": "Exportação em andamento...",
+ "export.done": "Modelo exportado e registrado como \"{{name}}\"",
+ "export.failed": "A exportação falhou: {{message}}",
+ "export.unknown": "erro desconhecido",
+ "export.nextTitle": "{{name}} agora é um modelo",
+ "export.nextText": "Ele está registrado no LocalAI. Abra um chat com ele ou encontre-o em Modelos.",
+ "export.chat": "Conversar com {{name}}",
+ "export.models": "Abrir em Modelos",
+ "export.archive": "Baixar arquivo",
+ "jobs.title": "Jobs anteriores",
+ "jobs.count_one": "{{count}} mantido neste servidor",
+ "jobs.count_other": "{{count}} mantidos neste servidor",
+ "jobs.loading": "Carregando jobs",
+ "jobs.emptyTitle": "Nenhum job de ajuste fino ainda",
+ "jobs.emptyText": "Configure um acima. Ele aparece aqui com o seu status, e você pode reutilizar a configuração depois.",
+ "jobs.model": "Modelo",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Status",
+ "jobs.started": "Iniciado",
+ "jobs.actions": "Ações",
+ "jobs.open": "Abrir este job",
+ "jobs.reuse": "Reutilizar",
+ "jobs.reuseTitle": "Usar a configuração deste job em um novo job",
+ "jobs.delete": "Excluir",
+ "jobs.deleteTitle": "Excluir este job e os seus dados",
+ "jobs.deleteDialogTitle": "Excluir este job?",
+ "jobs.deleteDialogText": "Isto exclui o job com os seus checkpoints e qualquer modelo exportado. Não pode ser desfeito.",
+ "off.badge": "Não disponível para você",
+ "off.fineTune.title": "Sua conta não pode fazer ajuste fino",
+ "off.fineTune.text": "O ajuste fino é uma permissão separada, e esta conta não a tem.",
+ "off.quantize.title": "Sua conta não pode quantizar",
+ "off.quantize.text": "A quantização é uma permissão separada, e esta conta não a tem.",
+ "off.admin": "Um administrador pode ativá-la em Usuários e chaves.",
+ "off.askAdmin": "Peça a um administrador para ativá-la na sua conta.",
+ "off.back": "Ir para o Início",
+ "landing.experimental": "Experimental",
+ "run.unknownPercent": "Progresso ainda não informado",
+ "export.adminOnly.title": "Exportar um modelo é função dos administradores",
+ "export.adminOnly.text": "Exportar adiciona um modelo a este servidor para todos que podem usá-lo. Peça a um administrador para exportar este trabalho."
+}
diff --git a/addons/fine-tuning/ui/src/i18n/fine-tuning.zh-CN.json b/addons/fine-tuning/ui/src/i18n/fine-tuning.zh-CN.json
new file mode 100644
index 0000000000..69f7e4e446
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/fine-tuning.zh-CN.json
@@ -0,0 +1,295 @@
+{
+ "steps.label": "进度",
+ "steps.setup": "设置",
+ "steps.check": "检查",
+ "steps.run": "运行",
+ "steps.result": "结果",
+ "run.stop": "停止",
+ "run.connecting": "正在连接任务。等待第一个进度事件。",
+ "run.progress": "进度",
+ "run.stages": "阶段",
+ "run.step": "步",
+ "run.of": "/",
+ "run.epoch": "轮次",
+ "run.tokens": "tokens/s",
+ "run.eta": "约剩 {{time}}",
+ "run.etaSeconds": "约剩 {{seconds}} 秒",
+ "checks.heading": "开始前检查",
+ "checks.live": "随您修改设置而更新。",
+ "checks.meterLabel": "已用 {{used}}%,{{total}} 中 {{free}} 可用",
+ "checks.openBackends": "打开后端",
+ "checks.barOk": "没有任何项目阻止启动。",
+ "checks.barWarn_one": "{{count}} 条警告。仍可以启动。",
+ "checks.barWarn_other": "{{count}} 条警告。仍可以启动。",
+ "checks.title.model": "模型",
+ "checks.title.data": "数据集",
+ "checks.title.backend": "后端",
+ "checks.title.memory": "内存",
+ "checks.title.disk": "磁盘",
+ "checks.title.reward": "奖励函数",
+ "checks.title.resume": "恢复",
+ "checks.title.access": "模型访问",
+ "checks.title.type": "量化类型",
+ "checks.title.source": "来源",
+ "checks.title.name": "名称",
+ "checks.fineTune.modelOk": "正在训练 {{model}}。",
+ "checks.fineTune.modelMissing": "请输入要训练的模型。",
+ "checks.fineTune.dataOk": "数据来自 {{source}}。",
+ "checks.fineTune.dataMissing": "请添加数据集:Hugging Face id 或上传的文件。",
+ "checks.fineTune.backendOk": "{{name}} 已安装。",
+ "checks.fineTune.backendMissing": "未安装微调后端,任务可能无法启动。",
+ "checks.fineTune.backendOther": "{{name}} 不在已安装的微调后端({{list}})中。",
+ "checks.fineTune.backendUnknown": "无法读取已安装的后端。",
+ "checks.fineTune.memoryGpu": "{{name}}:{{total}} 中 {{free}} 可用。批次大小 {{batch}},序列长度 {{seq}},梯度累积 {{accum}}。任务所需的内存取决于模型和这些设置,LocalAI 在任务开始前不做估算。",
+ "checks.fineTune.memoryCluster": "最佳节点({{node}})上的 {{total}}。批次大小 {{batch}},序列长度 {{seq}}。任务所需的内存取决于模型和这些设置。",
+ "checks.fineTune.memoryCpu": "未找到 GPU。训练使用 CPU,RAM {{total}} 中有 {{free}} 可用。预计会很慢。批次大小 {{batch}},序列长度 {{seq}}。",
+ "checks.fineTune.memoryUnknown": "此账户无法读取机器的内存。批次大小 {{batch}},序列长度 {{seq}}。任务所需的内存取决于模型和这些设置。",
+ "checks.fineTune.disk": "模型磁盘上可用 {{free}}。下载和检查点需要空间。",
+ "checks.fineTune.diskLow": "模型磁盘上仅剩 {{free}} 可用。下载和检查点需要空间。",
+ "checks.fineTune.rewardMissing": "GRPO 至少需要一个奖励函数。请在“更多选项”中添加。",
+ "checks.fineTune.resume": "训练将从 {{path}} 继续。",
+ "checks.fineTune.accessNone": "未设置令牌。受限模型需要在“更多选项”中设置 Hugging Face 令牌。",
+ "checks.fineTune.accessSet": "已设置 Hugging Face 令牌。",
+ "checks.quantize.modelOk": "正在量化 {{model}}。",
+ "checks.quantize.modelMissing": "请输入要量化的模型。",
+ "checks.quantize.typeMissing": "请输入自定义量化类型。",
+ "checks.quantize.backendOk": "{{name}} 已安装。",
+ "checks.quantize.backendMissing": "未安装量化后端,任务可能无法启动。",
+ "checks.quantize.backendOther": "{{name}} 不在已安装的量化后端({{list}})中。",
+ "checks.quantize.backendUnknown": "无法读取已安装的后端。",
+ "checks.quantize.memory": "RAM {{total}} 中有 {{free}} 可用。转换在 CPU 上运行。",
+ "checks.quantize.disk": "模型磁盘上可用 {{free}}。下载、转换后的文件和结果都会写入磁盘。",
+ "checks.quantize.diskLow": "模型磁盘上仅剩 {{free}} 可用。下载、转换后的文件和结果都会写入磁盘。",
+ "checks.quantize.accessNone": "未设置令牌。受限模型需要在“更多选项”中设置 Hugging Face 令牌。",
+ "checks.quantize.accessSet": "已设置 Hugging Face 令牌。",
+ "checks.import.sourceOk_huggingface": "识别为 Hugging Face 仓库。导入时 LocalAI 会检查其中的文件。",
+ "checks.import.sourceOk_url": "已识别 URL 格式。其内容尚未检查。",
+ "checks.import.sourceOk_config": "识别为模型配置文件。将按原样读取并使用。",
+ "checks.import.sourceOk_oci": "识别为 OCI 镜像引用。",
+ "checks.import.sourceOk_ollama": "识别为 Ollama 模型引用。",
+ "checks.import.sourceOk_local": "识别为运行 LocalAI 的机器上的路径。",
+ "checks.import.sourceOk": "已识别来源。",
+ "checks.import.sourceUnknown": "这看起来不是 LocalAI 支持的任何格式。仍会尝试,但可能失败。",
+ "checks.import.backendDownload": "{{name}} 尚未安装。LocalAI 会先下载它。",
+ "checks.import.backendChosen": "已选择并安装 {{name}}。",
+ "checks.import.backendAuto": "导入开始时,LocalAI 会根据来源的文件选择后端。",
+ "checks.import.nameTaken": "名为 {{name}} 的模型已安装。",
+ "checks.import.nameFree": "{{name}} 未被使用。",
+ "checks.import.nameAuto": "模型将以来源命名。",
+ "checks.import.disk": "模型磁盘上可用 {{free}}。导入开始后即可知道下载大小。",
+ "checks.import.memory_gpu": "当前 GPU 显存可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory_ram": "当前 RAM 可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory_cluster": "最佳节点上可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory": "当前内存可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "form.key": "键",
+ "form.value": "值",
+ "form.addOption": "添加选项",
+ "form.copy": "复制到剪贴板",
+ "form.more": "更多选项",
+ "form.moreHint": "方法、后端、适配器、优化器、评估、奖励函数",
+ "form.baseModel": "基础模型",
+ "form.model": "模型",
+ "form.modelHint": "Hugging Face id,或本机上的路径。",
+ "form.data": "您的数据",
+ "form.dataset": "数据集来源",
+ "form.datasetHint": "Hugging Face 数据集 id,或上传文件。",
+ "form.split": "划分",
+ "form.upload": "改为上传文件",
+ "form.uploadHint": ".json、.jsonl 或 .csv",
+ "form.removeFile": "移除文件",
+ "form.train": "训练内容",
+ "form.kind": "训练类型",
+ "form.kinds.adapter.title": "适配器",
+ "form.kinds.adapter.text": "训练一个小型适配器,保持基础权重不变。这是常见的选择,所需内存很少。",
+ "form.kinds.full.title": "完整模型",
+ "form.kinds.full.text": "修改每一个权重。所需内存是适配器的数倍。",
+ "form.epochs": "轮次",
+ "form.batch": "批次大小",
+ "form.lr": "学习率",
+ "form.backend": "后端",
+ "form.method": "训练方法",
+ "form.adapterKind": "适配器类型",
+ "form.rank": "秩",
+ "form.alpha": "Alpha",
+ "form.dropout": "Dropout",
+ "form.gradAccum": "梯度累积步数",
+ "form.warmup": "预热步数",
+ "form.seqLen": "最大序列长度",
+ "form.optimizer": "优化器",
+ "form.maxSteps": "最大步数",
+ "form.maxStepsHint": "0 表示自动。",
+ "form.saveSteps": "保存步数",
+ "form.saveLimit": "保存总数上限",
+ "form.saveLimitHint": "0 表示保留所有检查点。",
+ "form.weightDecay": "权重衰减",
+ "form.seed": "随机种子",
+ "form.seedHint": "0 表示随机选择种子。",
+ "form.precision": "混合精度",
+ "form.auto": "自动",
+ "form.targetModules": "目标模块",
+ "form.targetModulesHint": "以逗号分隔。留空则使用后端默认值。",
+ "form.gradCheckpointing": "梯度检查点(较慢,但占用更少内存)",
+ "form.token": "Hugging Face 令牌",
+ "form.tokenHint": "仅受限模型需要。",
+ "form.evalEnable": "训练期间评估",
+ "form.evalStrategy": "评估策略",
+ "form.evalSteps_": "步",
+ "form.evalEpoch": "轮次",
+ "form.evalSteps": "评估步数",
+ "form.evalStepsHint": "0 表示与保存步数相同。",
+ "form.evalSplit": "评估划分",
+ "form.evalDataset": "评估数据集",
+ "form.evalDatasetPlaceholder": "单独的数据集",
+ "form.evalRatio": "自动划分比例",
+ "form.rewards": "奖励函数(GRPO)",
+ "form.rewardsHint": "GRPO 至少需要一个奖励函数。可选择内置函数,或添加您自己的。",
+ "form.resume": "从检查点恢复",
+ "form.clearCheckpoint": "清除检查点",
+ "form.extra": "额外选项",
+ "form.extraHint": "特定于后端的键值对。",
+ "form.summaryFull": "完整模型",
+ "form.summary_lora": "LoRA 适配器",
+ "form.summary_loha": "LoHa 适配器",
+ "form.summary_lokr": "LoKr 适配器",
+ "form.recipe": "使用 {{method}} 在 {{data}} 上训练 {{model}} 的 {{kind}},共 {{epochs}} 轮。",
+ "form.recipeEmpty": "选择模型和数据集,即可查看将要训练的内容。",
+ "rewards.format_reward": "检查 ... 再接答案的格式。",
+ "rewards.reasoning_accuracy_reward": "将 的内容与数据集的答案列进行比较。",
+ "rewards.length_reward": "评估答案与目标长度的接近程度。",
+ "rewards.xml_tag_reward": "评估 XML 标签是否正确地成对打开和关闭。",
+ "rewards.no_repetition_reward": "对重复的 n-gram 进行扣分。",
+ "rewards.code_execution_reward": "检查 Python 代码块的语法是否有效。",
+ "rewards.param_target_length": "目标长度",
+ "rewards.fnName": "函数名",
+ "rewards.fnBody": "函数体",
+ "rewards.fnHint": "接收 completions 和 kwargs,并必须返回浮点数列表。可以使用 re、math、json 和 string。",
+ "rewards.add": "添加",
+ "rewards.cancel": "取消",
+ "rewards.custom": "添加自定义奖励函数",
+ "fineTune.title": "微调",
+ "fineTune.lede": "用您自己的数据训练适配器,然后将其导出为可以对话的模型。",
+ "fineTune.importConfig": "导入配置",
+ "fineTune.exportConfig": "导出配置",
+ "fineTune.newJob": "新建任务",
+ "fineTune.badConfig": "无法读取该文件。请使用从此页面导出的 JSON 配置。",
+ "fineTune.start": "开始微调",
+ "fineTune.starting": "正在启动...",
+ "fineTune.resume": "恢复训练",
+ "fineTune.resuming": "正在从检查点恢复:",
+ "fineTune.clear": "清除",
+ "fineTune.barNote": "在您开始之前,不会更改任何内容。",
+ "fineTune.jobMeta": "{{backend}} 上的 {{kind}} {{method}},开始于 {{started}}",
+ "fineTune.failed": "训练失败",
+ "fineTune.failedAt": "训练在第 {{step}} 步(共 {{total}} 步)时停止",
+ "fineTune.noMessage": "后端没有提供消息。",
+ "fineTune.finished": "训练完成。",
+ "fineTune.finishedLoss": "训练完成。最后的损失为 {{loss}}。",
+ "fineTune.stopped": "任务已停止。如果您选择保留,检查点会被保留。",
+ "fineTune.tryTitle": "下一步该怎么做",
+ "fineTune.tryText": "服务器报告内存不足。下面的每项更改都会应用到设置的副本上,在您点击“开始”之前不会启动任何内容。",
+ "fineTune.tryPlain": "重新打开设置,修改消息所指的内容,然后启动新任务。",
+ "fineTune.fixBatch": "将批次大小降至 1",
+ "fineTune.fixCheckpointing": "开启梯度检查点(较慢,但占用更少内存)",
+ "fineTune.retryChanges": "应用这些更改并打开设置",
+ "fineTune.reuse": "重新打开设置",
+ "fineTune.resumeFrom": "从第 {{step}} 步恢复",
+ "fineTune.status.queued": "排队中",
+ "fineTune.status.loading_model": "正在加载模型",
+ "fineTune.status.loading_dataset": "正在加载数据集",
+ "fineTune.status.training": "训练中",
+ "fineTune.status.saving": "保存中",
+ "fineTune.status.completed": "已完成",
+ "fineTune.status.failed": "失败",
+ "fineTune.status.stopped": "已停止",
+ "stop.title": "停止训练?",
+ "stop.text": "立即保存的检查点可让您恢复训练,或导出目前已学到的内容。",
+ "stop.keep": "停止并保留检查点",
+ "stop.discard": "停止且不保留检查点",
+ "stop.cancel": "继续训练",
+ "stop.cancelDelete": "取消",
+ "chart.loss": "损失",
+ "chart.learning_rate": "学习率",
+ "chart.grad_norm": "梯度范数",
+ "chart.metric": "指标",
+ "chart.step": "步",
+ "chart.eval": "评估损失",
+ "chart.waiting": "等待更多步数。",
+ "chart.window": "第 {{from}} 至 {{to}} 步,共 {{total}} 步",
+ "chart.windowEval": "第 {{from}} 至 {{to}} 步,共 {{total}} 步。空心点为评估损失。",
+ "chart.aria": "训练步数上的 {{metric}}。第 {{step}} 步的最后一个值为 {{last}}。",
+ "chart.readout": "第 {{step}} 步:{{value}}",
+ "chart.dataTitle": "显示数值",
+ "chart.emptyText": "任务报告两个步骤后,图表即会绘制。",
+ "log.title": "日志",
+ "log.note": "自打开此页面以来任务报告的内容。",
+ "log.only": "仅显示警告和错误",
+ "log.copy": "复制",
+ "log.copied": "已复制",
+ "log.download": "下载",
+ "log.empty": "尚无报告。",
+ "log.noneFiltered": "没有警告或错误。",
+ "checkpoints.title": "检查点",
+ "checkpoints.step": "步",
+ "checkpoints.epoch": "轮次",
+ "checkpoints.loss": "损失",
+ "checkpoints.path": "路径",
+ "checkpoints.actions": "操作",
+ "checkpoints.resume": "恢复",
+ "checkpoints.resumeTitle": "从此检查点恢复训练",
+ "checkpoints.export": "导出",
+ "checkpoints.exportTitle": "导出此检查点",
+ "export.blocked": "此服务器未与附加组件主机共享模型文件夹,因此已关闭导出。",
+ "export.title": "导出为模型",
+ "export.lede": "导出会将结果转换为 LocalAI 可以加载的模型。",
+ "export.checkpoint": "检查点",
+ "export.final": "最终模型(输出目录)",
+ "export.checkpointOption": "第 {{step}} 步(损失 {{loss}})",
+ "export.format": "导出格式",
+ "export.lora": "LoRA 适配器",
+ "export.merged16": "合并(16 位)",
+ "export.merged4": "合并(4 位)",
+ "export.quant": "量化",
+ "export.name": "模型名称",
+ "export.nameHint": "留空则根据任务自动生成。",
+ "export.go": "导出",
+ "export.exporting": "正在导出...",
+ "export.working": "正在导出...",
+ "export.done": "模型已导出并注册为“{{name}}”",
+ "export.failed": "导出失败:{{message}}",
+ "export.unknown": "未知错误",
+ "export.nextTitle": "{{name}} 现已成为模型",
+ "export.nextText": "它已在 LocalAI 中注册。打开与它的对话,或在模型页面中找到它。",
+ "export.chat": "与 {{name}} 对话",
+ "export.models": "在模型中打开",
+ "export.archive": "下载归档",
+ "jobs.title": "早前的任务",
+ "jobs.count_one": "此服务器保留 {{count}} 个",
+ "jobs.count_other": "此服务器保留 {{count}} 个",
+ "jobs.loading": "正在加载任务",
+ "jobs.emptyTitle": "还没有微调任务",
+ "jobs.emptyText": "在上方进行设置。任务会连同状态显示在这里,之后您可以复用其设置。",
+ "jobs.model": "模型",
+ "jobs.kind": "类型",
+ "jobs.status": "状态",
+ "jobs.started": "开始时间",
+ "jobs.actions": "操作",
+ "jobs.open": "打开此任务",
+ "jobs.reuse": "复用",
+ "jobs.reuseTitle": "将此任务的设置用于新任务",
+ "jobs.delete": "删除",
+ "jobs.deleteTitle": "删除此任务及其数据",
+ "jobs.deleteDialogTitle": "删除此任务?",
+ "jobs.deleteDialogText": "这将删除任务及其检查点和任何已导出的模型。此操作无法撤销。",
+ "off.badge": "您无法使用",
+ "off.fineTune.title": "您的账户无法进行微调",
+ "off.fineTune.text": "微调是一项单独的权限,此账户没有该权限。",
+ "off.quantize.title": "您的账户无法进行量化",
+ "off.quantize.text": "量化是一项单独的权限,此账户没有该权限。",
+ "off.admin": "管理员可以在“用户和密钥”中开启该权限。",
+ "off.askAdmin": "请管理员为您的账户开启该权限。",
+ "off.back": "前往首页",
+ "landing.experimental": "实验性",
+ "run.unknownPercent": "尚未报告进度",
+ "export.adminOnly.title": "导出模型仅限管理员",
+ "export.adminOnly.text": "导出会为所有可使用此服务器的人添加一个模型。请让管理员导出此任务。"
+}
diff --git a/addons/fine-tuning/ui/src/i18n/index.js b/addons/fine-tuning/ui/src/i18n/index.js
new file mode 100644
index 0000000000..ed7c6df1f9
--- /dev/null
+++ b/addons/fine-tuning/ui/src/i18n/index.js
@@ -0,0 +1,16 @@
+// The words of the fine-tuning pages, one JSON bundle per language of the web UI.
+// English is the source and the fallback of every key. The texts repeat the
+// host's own tools.json for each language; the few keys the add-on owns
+// are drafts that want a native review. addons/internal/i18ncheck
+// checks that every bundle has the keys and placeholders of English.
+import en from './fine-tuning.en.json'
+import it from './fine-tuning.it.json'
+import de from './fine-tuning.de.json'
+import es from './fine-tuning.es.json'
+import fr from './fine-tuning.fr.json'
+import id from './fine-tuning.id.json'
+import ko from './fine-tuning.ko.json'
+import pt_BR from './fine-tuning.pt-BR.json'
+import zh_CN from './fine-tuning.zh-CN.json'
+
+export const locales = { en, it, de, es, fr, id, ko, 'pt-BR': pt_BR, 'zh-CN': zh_CN }
diff --git a/addons/fine-tuning/ui/test/page.test.mjs b/addons/fine-tuning/ui/test/page.test.mjs
index b376d754f2..418463c123 100644
--- a/addons/fine-tuning/ui/test/page.test.mjs
+++ b/addons/fine-tuning/ui/test/page.test.mjs
@@ -4,9 +4,9 @@ import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { load, draw, here, hostUi, React, renderToString } from './load.mjs'
import { createApi } from '../src/api.js'
-import en from '../src/i18n/en.js'
const read = (p) => readFileSync(p, 'utf8')
+const en = JSON.parse(read(join(here, '../src/i18n/fine-tuning.en.json')))
const src = join(here, '../src')
const toolsUi = join(here, '../../../internal/tools-ui/src')
const files = (dir) => readdirSync(dir, { recursive: true }).filter(f => /\.(jsx?|mjs)$/.test(f)).map(f => join(dir, f))
@@ -208,7 +208,7 @@ test('plural: the job count picks the singular and the plural key', async () =>
assert.equal(ctx.plural('jobs.count', 3), '3 kept on this server')
})
-test('i18n: every literal key the add-on and the shared views read is in en.js', () => {
+test('i18n: every literal key the add-on and the shared views read is in the English bundle', () => {
const keys = new Set()
for (const text of [ownText, sharedText]) for (const m of text.matchAll(/\bt\(\s*'([A-Za-z0-9_.]+)'/g)) keys.add(m[1])
assert.ok(keys.size > 100, `found only ${keys.size} literal keys`)
@@ -216,7 +216,7 @@ test('i18n: every literal key the add-on and the shared views read is in en.js',
assert.deepEqual(missing, [])
})
-test('i18n: en.js repeats the host strings and adds only the keys the add-on owns', () => {
+test('i18n: the English bundle repeats the host strings and adds only the keys the add-on owns', () => {
const tools = Object.fromEntries(flatten(JSON.parse(read(join(hostUi, 'public/locales/en/tools.json')))))
const own = []
for (const [k, v] of Object.entries(en)) {
diff --git a/addons/memory/ui/src/i18n/index.js b/addons/memory/ui/src/i18n/index.js
new file mode 100644
index 0000000000..2444861064
--- /dev/null
+++ b/addons/memory/ui/src/i18n/index.js
@@ -0,0 +1,16 @@
+// The words of the memory page, one JSON bundle per language of the web UI.
+// English is the source and the fallback of every key. Italian is a draft that
+// wants a native review; the other bundles repeat English except for the few
+// strings the host already translated. addons/internal/i18ncheck checks that
+// every bundle has the keys and placeholders of English.
+import en from './memory.en.json'
+import it from './memory.it.json'
+import de from './memory.de.json'
+import es from './memory.es.json'
+import fr from './memory.fr.json'
+import id from './memory.id.json'
+import ko from './memory.ko.json'
+import pt_BR from './memory.pt-BR.json'
+import zh_CN from './memory.zh-CN.json'
+
+export const locales = { en, it, de, es, fr, id, ko, 'pt-BR': pt_BR, 'zh-CN': zh_CN }
diff --git a/addons/memory/ui/src/i18n/memory.de.json b/addons/memory/ui/src/i18n/memory.de.json
new file mode 100644
index 0000000000..c01b85e0f5
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.de.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Eine Notiz speichern",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Erst fragen",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Speichern und Bescheid geben",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Nie",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.en.json b/addons/memory/ui/src/i18n/memory.en.json
new file mode 100644
index 0000000000..f49f5298d8
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.en.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Saving a note",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Ask me first",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Save it and tell me",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Never",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.es.json b/addons/memory/ui/src/i18n/memory.es.json
new file mode 100644
index 0000000000..8de68dea61
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.es.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Guardar una nota",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Preguntarme antes",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Guardar y avisarme",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Nunca",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.fr.json b/addons/memory/ui/src/i18n/memory.fr.json
new file mode 100644
index 0000000000..6a0b0b9999
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.fr.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Enregistrer une note",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Me demander d'abord",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Enregistrer et me prévenir",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Jamais",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.id.json b/addons/memory/ui/src/i18n/memory.id.json
new file mode 100644
index 0000000000..2e7c5a45c1
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.id.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Menyimpan catatan",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Tanya saya dulu",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Simpan dan beri tahu saya",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Jangan pernah",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.it.json b/addons/memory/ui/src/i18n/memory.it.json
new file mode 100644
index 0000000000..b2f789e8d5
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.it.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Memoria breve",
+ "memory.intro": "Le tue chat conservano brevi note su di te: dove vivi, cosa ti piace, come vuoi le risposte. Prima di rispondere, il modello legge la nota che corrisponde alla domanda. Non devi chiederglielo.",
+ "memory.rules.confirm": "Per impostazione predefinita una nuova nota viene salvata solo dopo la tua conferma. Una proposta resta in attesa per un giorno, poi scade. Puoi cambiarlo qui sotto.",
+ "memory.rules.where": "Le note sono semplici file nella cartella Memoria dei tuoi file. Puoi leggerle, modificarle ed eliminarle lì, oppure nella scheda Memoria del tuo account.",
+ "memory.rules.scopes": "Ci sono tre tipi di note: le tue per ogni chat, una per ogni agente salvato e una per ogni conversazione che arriva da un'app di messaggistica. Un agente salvato legge le note solo se gli consenti questo add-on.",
+ "memory.rules.limits": "Il modello legge un breve elenco, al massimo 40 righe per tipo, e apre un argomento quando gli servono i dettagli.",
+ "memory.open": "Apri le mie note",
+ "memory.builtin.title": "La memoria è integrata",
+ "memory.builtin.reason": "La memoria ora fa parte di LocalAI e per usarla non ti serve questo add-on. Scegli come salvare le note in Impostazioni > Memoria.",
+ "memory.builtin.fix": "Apri Impostazioni > Memoria",
+ "memory.save.title": "Salvare una nota",
+ "memory.save.legend": "Quando l'assistente vuole salvare qualcosa nella mia memoria",
+ "memory.save.ask": "Chiedimi prima",
+ "memory.save.ask.desc": "L'assistente propone la nota e aspetta. La confermi nella chat, oppure rispondi sì o no in un'app di messaggistica. È l'impostazione predefinita.",
+ "memory.save.auto": "Salva e avvisami",
+ "memory.save.auto.desc": "Nelle tue chat la nota viene salvata subito e la chat mostra «Salvato in memoria» con un Annulla. Una risposta salva al massimo 5 note. Una nota lunga, o che sembra contenere un segreto, aspetta comunque te.",
+ "memory.save.never": "Mai",
+ "memory.save.never.desc": "L'assistente non può proporre una nota. Se gli chiedi di ricordare qualcosa, dice che il salvataggio è disattivato.",
+ "memory.save.exception": "Le chat che arrivano da un'app di messaggistica chiedono sempre prima, qualunque cosa scelga qui, perché chi ha scritto il messaggio non sei sempre tu. Le esecuzioni pianificate e in background non possono salvare note. Un agente salvato non può allargare la tua scelta: se scegli di essere interpellato, chiede, anche se gli hai permesso di scrivere in memoria.",
+ "memory.save.loading": "Lettura della tua scelta",
+ "memory.save.saving": "Salvataggio della tua scelta",
+ "memory.save.saved": "La tua scelta è stata salvata.",
+ "memory.save.failed": "Non è stato possibile salvare la tua scelta. Resta valida la scelta precedente. Riprova.",
+ "memory.save.unreadable": "Non è stato possibile leggere la tua scelta. Ricarica la pagina per riprovare."
+}
diff --git a/addons/memory/ui/src/i18n/memory.ko.json b/addons/memory/ui/src/i18n/memory.ko.json
new file mode 100644
index 0000000000..e873980b93
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.ko.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "메모 저장",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "먼저 물어보기",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "저장하고 알려주기",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "안 함",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.pt-BR.json b/addons/memory/ui/src/i18n/memory.pt-BR.json
new file mode 100644
index 0000000000..4cf705b46e
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.pt-BR.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "Salvar uma nota",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "Perguntar antes",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "Salvar e me avisar",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "Nunca",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/i18n/memory.zh-CN.json b/addons/memory/ui/src/i18n/memory.zh-CN.json
new file mode 100644
index 0000000000..e043e68382
--- /dev/null
+++ b/addons/memory/ui/src/i18n/memory.zh-CN.json
@@ -0,0 +1,26 @@
+{
+ "memory.title": "Short memory",
+ "memory.intro": "Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.",
+ "memory.rules.confirm": "By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.",
+ "memory.rules.where": "The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.",
+ "memory.rules.scopes": "There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.",
+ "memory.rules.limits": "The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.",
+ "memory.open": "Open my notes",
+ "memory.builtin.title": "Memory is built in",
+ "memory.builtin.reason": "Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.",
+ "memory.builtin.fix": "Open Settings > Memory",
+ "memory.save.title": "保存笔记",
+ "memory.save.legend": "When the assistant wants to save something to my memory",
+ "memory.save.ask": "先问我",
+ "memory.save.ask.desc": "The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.",
+ "memory.save.auto": "保存并告知我",
+ "memory.save.auto.desc": "In your own chats the note is saved at once, and the chat shows \"Saved to memory\" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.",
+ "memory.save.never": "从不",
+ "memory.save.never.desc": "The assistant cannot propose a note. It says that saving is off if you ask it to remember something.",
+ "memory.save.exception": "Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.",
+ "memory.save.loading": "Reading your choice",
+ "memory.save.saving": "Saving your choice",
+ "memory.save.saved": "Your choice is saved.",
+ "memory.save.failed": "Your choice could not be saved. The earlier choice is still in force. Try again.",
+ "memory.save.unreadable": "Your choice could not be read. Reload the page to try again."
+}
diff --git a/addons/memory/ui/src/index.jsx b/addons/memory/ui/src/index.jsx
index a3a9ea79b6..bd8b415005 100644
--- a/addons/memory/ui/src/index.jsx
+++ b/addons/memory/ui/src/index.jsx
@@ -8,6 +8,8 @@
// userSettings, key save-mode); the add-on does not store it and the host
// validates it. The rules that make it safe are the host's: this page only
// states them.
+import { locales } from './i18n/index.js'
+
export const NOTES_ADDRESS = '/app/account?tab=memory'
// Memory is built in; the save level lives in Settings > Memory.
export const SETTINGS_ADDRESS = '/app/account?tab=memory'
@@ -17,32 +19,7 @@ export const SAVE_MODES = ['ask', 'auto', 'never']
export function createAddon(sdk) {
const { React } = sdk
const h = React.createElement
- sdk.locale.define('en', {
- 'memory.title': 'Short memory',
- 'memory.intro': 'Your chats keep short notes about you: where you live, what you like, how you want answers. Before a reply, the model reads the note that matches the question. You do not have to ask it to.',
- 'memory.rules.confirm': 'By default a new note is saved only after you confirm it. A proposal waits for one day, then it expires. You can change this below.',
- 'memory.rules.where': 'The notes are plain files in the Memory folder of your files. You can read, edit and delete them there, or on the Memory tab of your account.',
- 'memory.rules.scopes': 'There are three kinds of notes: yours for every chat, one for each saved agent, and one for each conversation that comes from a messaging app. A saved agent reads notes only when you allow this add-on for it.',
- 'memory.rules.limits': 'The model reads a short list, at most 40 lines for each kind, and opens a topic when it needs the details.',
- 'memory.open': 'Open my notes',
- 'memory.builtin.title': 'Memory is built in',
- 'memory.builtin.reason': 'Memory is part of LocalAI now, and you do not need this add-on for it. Choose how notes are saved in Settings > Memory.',
- 'memory.builtin.fix': 'Open Settings > Memory',
- 'memory.save.title': 'Saving a note',
- 'memory.save.legend': 'When the assistant wants to save something to my memory',
- 'memory.save.ask': 'Ask me first',
- 'memory.save.ask.desc': 'The assistant proposes the note and waits. You confirm it in the chat, or answer yes or no in a messaging app. This is the default.',
- 'memory.save.auto': 'Save it and tell me',
- 'memory.save.auto.desc': 'In your own chats the note is saved at once, and the chat shows "Saved to memory" with an Undo. A reply saves at most 5 notes. A long note, or one that looks like it holds a secret, still waits for you.',
- 'memory.save.never': 'Never',
- 'memory.save.never.desc': 'The assistant cannot propose a note. It says that saving is off if you ask it to remember something.',
- 'memory.save.exception': 'Chats that come from a messaging app always ask first, whatever you choose here, because the person who wrote the message is not always you. Scheduled and background runs cannot save notes. A saved agent cannot widen your choice: if you choose to be asked, it asks, even when you let it write to memory.',
- 'memory.save.loading': 'Reading your choice',
- 'memory.save.saving': 'Saving your choice',
- 'memory.save.saved': 'Your choice is saved.',
- 'memory.save.failed': 'Your choice could not be saved. The earlier choice is still in force. Try again.',
- 'memory.save.unreadable': 'Your choice could not be read. Reload the page to try again.',
- })
+ for (const [language, bundle] of Object.entries(locales)) sdk.locale.define(language, bundle)
const t = (key) => sdk.locale.t(key, key)
// The picker. It shows the choice the host reports, never one it assumed, and
diff --git a/addons/memory/ui/test/page.test.mjs b/addons/memory/ui/test/page.test.mjs
index 3f1173ded7..ecef0f57d9 100644
--- a/addons/memory/ui/test/page.test.mjs
+++ b/addons/memory/ui/test/page.test.mjs
@@ -35,3 +35,16 @@ test('page: the button opens the Memory tab of the account', async () => {
find(tree).props.onClick()
assert.deepEqual(opened, [mod.NOTES_ADDRESS || '/app/account?tab=memory'])
})
+
+test('page: it reads in the language of the web UI, and in English for a language it has no text for', async () => {
+ const it = await load({ language: () => 'it' })
+ const italian = draw(it.mod.createAddon(it.sdk), { page: 'memory' })
+ assert.match(italian, /Memoria breve/)
+ assert.match(italian, /Apri le mie note/)
+ assert.doesNotMatch(italian, /Short memory/)
+ const de = await load({ language: () => 'de' })
+ const german = draw(de.mod.createAddon(de.sdk), { page: 'memory' })
+ assert.match(german, /Short memory/, 'German repeats the English title until it is translated')
+ const unknown = await load({ language: () => 'xx' })
+ assert.match(draw(unknown.mod.createAddon(unknown.sdk), { page: 'memory' }), /Short memory/)
+})
diff --git a/addons/quantization/ui/src/ctx.jsx b/addons/quantization/ui/src/ctx.jsx
index be3969d726..f0f9855a8f 100644
--- a/addons/quantization/ui/src/ctx.jsx
+++ b/addons/quantization/ui/src/ctx.jsx
@@ -1,4 +1,4 @@
-import en from './i18n/en.js'
+import { locales } from './i18n/index.js'
import { modelFrom as pickModel } from './lib/model.js'
// The one object every factory of the quantization add-on receives. It binds the
@@ -10,7 +10,9 @@ export function createContext(sdk) {
const { React } = sdk
const h = React.createElement
const Fragment = React.Fragment
- sdk.locale.define('en', Object.fromEntries(Object.entries(en).filter(([, v]) => typeof v === 'string')))
+ // Every language of the page; a key a language lacks reads in English.
+ for (const [language, bundle] of Object.entries(locales)) sdk.locale.define(language, bundle)
+ const en = locales.en
const t = (key, vars) => sdk.locale.t(key, key, vars)
// The text of a key, or the fallback when the bundle has none.
diff --git a/addons/quantization/ui/src/i18n/index.js b/addons/quantization/ui/src/i18n/index.js
new file mode 100644
index 0000000000..a4d5c313cf
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/index.js
@@ -0,0 +1,16 @@
+// The words of the quantization pages, one JSON bundle per language of the web UI.
+// English is the source and the fallback of every key. The texts repeat the
+// host's own tools.json for each language; the few keys the add-on owns
+// are drafts that want a native review. addons/internal/i18ncheck
+// checks that every bundle has the keys and placeholders of English.
+import en from './quantization.en.json'
+import it from './quantization.it.json'
+import de from './quantization.de.json'
+import es from './quantization.es.json'
+import fr from './quantization.fr.json'
+import id from './quantization.id.json'
+import ko from './quantization.ko.json'
+import pt_BR from './quantization.pt-BR.json'
+import zh_CN from './quantization.zh-CN.json'
+
+export const locales = { en, it, de, es, fr, id, ko, 'pt-BR': pt_BR, 'zh-CN': zh_CN }
diff --git a/addons/quantization/ui/src/i18n/quantization.de.json b/addons/quantization/ui/src/i18n/quantization.de.json
new file mode 100644
index 0000000000..ea6f1fb7d8
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.de.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Fortschritt",
+ "steps.setup": "Einrichten",
+ "steps.check": "Prüfen",
+ "steps.run": "Ausführen",
+ "steps.result": "Ergebnis",
+ "run.stop": "Stoppen",
+ "run.connecting": "Verbindung zum Job wird hergestellt. Warten auf das erste Fortschrittsereignis.",
+ "run.progress": "Fortschritt",
+ "run.stages": "Phasen",
+ "run.step": "Schritt",
+ "run.of": "von",
+ "run.epoch": "Epoche",
+ "run.tokens": "Token/s",
+ "run.eta": "noch etwa {{time}}",
+ "run.etaSeconds": "noch etwa {{seconds}} s",
+ "landing.experimental": "Experimentell",
+ "checks.heading": "Vor dem Start prüfen",
+ "checks.live": "Wird aktualisiert, während Sie die Einrichtung ändern.",
+ "checks.meterLabel": "{{used}} Prozent belegt, {{free}} von {{total}} frei",
+ "checks.openBackends": "Backends öffnen",
+ "checks.barOk": "Nichts blockiert den Start.",
+ "checks.barWarn_one": "{{count}} Warnung. Sie können trotzdem starten.",
+ "checks.barWarn_other": "{{count}} Warnungen. Sie können trotzdem starten.",
+ "checks.title.model": "Modell",
+ "checks.title.data": "Datensatz",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Speicher",
+ "checks.title.disk": "Festplatte",
+ "checks.title.reward": "Belohnungsfunktionen",
+ "checks.title.resume": "Fortsetzen",
+ "checks.title.access": "Modellzugriff",
+ "checks.title.type": "Quantisierungstyp",
+ "checks.title.source": "Quelle",
+ "checks.title.name": "Name",
+ "checks.fineTune.modelOk": "Training von {{model}}.",
+ "checks.fineTune.modelMissing": "Geben Sie das Modell ein, das trainiert werden soll.",
+ "checks.fineTune.dataOk": "Die Daten kommen von {{source}}.",
+ "checks.fineTune.dataMissing": "Fügen Sie einen Datensatz hinzu: eine Hugging-Face-ID oder eine hochgeladene Datei.",
+ "checks.fineTune.backendOk": "{{name}} ist installiert.",
+ "checks.fineTune.backendMissing": "Kein Backend für Feinabstimmung ist installiert, der Job kann daher nicht starten.",
+ "checks.fineTune.backendOther": "{{name}} gehört nicht zu den installierten Backends für Feinabstimmung ({{list}}).",
+ "checks.fineTune.backendUnknown": "Die installierten Backends konnten nicht gelesen werden.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} von {{total}} frei. Batch-Größe {{batch}}, Sequenzlänge {{seq}}, Gradientenakkumulation {{accum}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab. LocalAI schätzt es vor dem Start des Jobs nicht.",
+ "checks.fineTune.memoryCluster": "{{total}} auf dem besten Knoten ({{node}}). Batch-Größe {{batch}}, Sequenzlänge {{seq}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab.",
+ "checks.fineTune.memoryCpu": "Keine GPU gefunden. Das Training läuft auf der CPU, und {{free}} von {{total}} RAM sind frei. Rechnen Sie mit Langsamkeit. Batch-Größe {{batch}}, Sequenzlänge {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Dieses Konto kann den Speicher des Rechners nicht lesen. Batch-Größe {{batch}}, Sequenzlänge {{seq}}. Wie viel der Job braucht, hängt vom Modell und diesen Einstellungen ab.",
+ "checks.fineTune.disk": "{{free}} frei auf dem Modell-Speicher. Downloads und Checkpoints brauchen Platz.",
+ "checks.fineTune.diskLow": "Nur {{free}} frei auf dem Modell-Speicher. Downloads und Checkpoints brauchen Platz.",
+ "checks.fineTune.rewardMissing": "GRPO braucht mindestens eine Belohnungsfunktion. Fügen Sie unter Weitere Optionen eine hinzu.",
+ "checks.fineTune.resume": "Das Training wird ab {{path}} fortgesetzt.",
+ "checks.fineTune.accessNone": "Kein Token gesetzt. Ein geschütztes Modell braucht ein Hugging-Face-Token unter Weitere Optionen.",
+ "checks.fineTune.accessSet": "Ein Hugging-Face-Token ist gesetzt.",
+ "checks.quantize.modelOk": "Quantisierung von {{model}}.",
+ "checks.quantize.modelMissing": "Geben Sie das Modell ein, das quantisiert werden soll.",
+ "checks.quantize.typeMissing": "Geben Sie den eigenen Quantisierungstyp ein.",
+ "checks.quantize.backendOk": "{{name}} ist installiert.",
+ "checks.quantize.backendMissing": "Kein Quantisierungs-Backend ist installiert, der Job kann daher nicht starten.",
+ "checks.quantize.backendOther": "{{name}} gehört nicht zu den installierten Quantisierungs-Backends ({{list}}).",
+ "checks.quantize.backendUnknown": "Die installierten Backends konnten nicht gelesen werden.",
+ "checks.quantize.memory": "{{free}} von {{total}} RAM sind frei. Die Umwandlung läuft auf der CPU.",
+ "checks.quantize.disk": "{{free}} frei auf dem Modell-Speicher. Download, umgewandelte Datei und Ergebnis werden auf die Festplatte geschrieben.",
+ "checks.quantize.diskLow": "Nur {{free}} frei auf dem Modell-Speicher. Download, umgewandelte Datei und Ergebnis werden auf die Festplatte geschrieben.",
+ "checks.quantize.accessNone": "Kein Token gesetzt. Ein geschütztes Modell braucht ein Hugging-Face-Token unter Weitere Optionen.",
+ "checks.quantize.accessSet": "Ein Hugging-Face-Token ist gesetzt.",
+ "checks.import.sourceOk_huggingface": "Erkannt als Hugging-Face-Repository. LocalAI prüft seine Dateien beim Import.",
+ "checks.import.sourceOk_url": "URL-Format erkannt. Der Inhalt wurde nicht geprüft.",
+ "checks.import.sourceOk_config": "Erkannt als Konfigurationsdatei eines Modells. Sie wird so gelesen und verwendet, wie sie ist.",
+ "checks.import.sourceOk_oci": "Erkannt als OCI-Image-Referenz.",
+ "checks.import.sourceOk_ollama": "Erkannt als Ollama-Modellreferenz.",
+ "checks.import.sourceOk_local": "Erkannt als Pfad auf dem Rechner, auf dem LocalAI läuft.",
+ "checks.import.sourceOk": "Die Quelle ist erkannt.",
+ "checks.import.sourceUnknown": "Das sieht nach keinem Format aus, das LocalAI kennt. Es wird trotzdem versucht und kann fehlschlagen.",
+ "checks.import.backendDownload": "{{name}} ist noch nicht installiert. LocalAI lädt es zuerst herunter.",
+ "checks.import.backendChosen": "{{name}} ist ausgewählt und wird installiert.",
+ "checks.import.backendAuto": "LocalAI wählt das Backend anhand der Dateien der Quelle, sobald der Import beginnt.",
+ "checks.import.nameTaken": "Ein Modell namens {{name}} ist bereits installiert.",
+ "checks.import.nameFree": "{{name}} ist nicht in Verwendung.",
+ "checks.import.nameAuto": "Das Modell erhält den Namen der Quelle.",
+ "checks.import.disk": "{{free}} frei auf dem Modell-Speicher. Die Download-Größe ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_gpu": "{{free}} GPU-Speicher sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_ram": "{{free}} RAM sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory_cluster": "{{free}} sind auf dem besten Knoten verfügbar. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "checks.import.memory": "{{free}} Speicher sind jetzt frei. Was dieses Modell braucht, ist bekannt, sobald der Import beginnt.",
+ "form.more": "Weitere Optionen",
+ "form.backend": "Backend",
+ "form.token": "Hugging-Face-Token",
+ "fineTune.starting": "Wird gestartet...",
+ "fineTune.noMessage": "Das Backend hat keine Meldung ausgegeben.",
+ "fineTune.tryTitle": "Was als Nächstes zu tun ist",
+ "fineTune.reuse": "Einrichtung erneut öffnen",
+ "stop.cancelDelete": "Abbrechen",
+ "log.title": "Protokoll",
+ "log.note": "Was der Job seit dem Öffnen dieser Seite gemeldet hat.",
+ "log.only": "Nur Warnungen und Fehler",
+ "log.copy": "Kopieren",
+ "log.copied": "Kopiert",
+ "log.download": "Herunterladen",
+ "log.empty": "Noch nichts gemeldet.",
+ "log.noneFiltered": "Keine Warnungen oder Fehler.",
+ "jobs.title": "Frühere Jobs",
+ "jobs.count_one": "{{count}} auf diesem Server gespeichert",
+ "jobs.count_other": "{{count}} auf diesem Server gespeichert",
+ "jobs.loading": "Jobs werden geladen",
+ "jobs.emptyTitle": "Noch keine Feinabstimmungs-Jobs",
+ "jobs.emptyText": "Richten Sie oben einen ein. Er erscheint hier mit seinem Status, und Sie können seine Einrichtung später wiederverwenden.",
+ "jobs.model": "Modell",
+ "jobs.kind": "Art",
+ "jobs.status": "Status",
+ "jobs.started": "Gestartet",
+ "jobs.actions": "Aktionen",
+ "jobs.open": "Diesen Job öffnen",
+ "jobs.reuse": "Wiederverwenden",
+ "jobs.reuseTitle": "Die Einrichtung dieses Jobs für einen neuen Job verwenden",
+ "jobs.delete": "Löschen",
+ "jobs.deleteTitle": "Diesen Job und seine Daten löschen",
+ "jobs.deleteDialogTitle": "Diesen Job löschen?",
+ "jobs.deleteDialogText": "Dies löscht den Job mit seinen Checkpoints und einem exportierten Modell. Das kann nicht rückgängig gemacht werden.",
+ "quantize.title": "Quantisieren",
+ "quantize.lede": "Machen Sie ein Modell kleiner, damit es in weniger Speicher passt. Das Ergebnis ist eine GGUF-Datei, die Sie importieren können.",
+ "quantize.newJob": "Neuer Job",
+ "quantize.sectionModel": "Modell",
+ "quantize.model": "Modell",
+ "quantize.modelHint": "Eine Hugging-Face-ID oder ein Pfad auf diesem Host.",
+ "quantize.sectionPrecision": "Genauigkeit",
+ "quantize.type": "Quantisierungstyp",
+ "quantize.typeHint": "Eine kleinere Zahl bedeutet eine kleinere Datei und etwas weniger Qualität.",
+ "quantize.custom": "Eigener...",
+ "quantize.customLabel": "Eigener Typ",
+ "quantize.customPlaceholder": "zum Beispiel iq4_nl",
+ "quantize.moreHint": "Backend, Hugging-Face-Token",
+ "quantize.tokenHint": "Nur für geschützte Modelle nötig.",
+ "quantize.recipe": "{{model}} in GGUF mit {{type}} umwandeln.",
+ "quantize.recipeEmpty": "Geben Sie ein Modell ein, um zu sehen, was erstellt wird.",
+ "quantize.start": "Quantisieren ({{type}})",
+ "quantize.barNote": "Nichts wird geändert, bis Sie starten.",
+ "quantize.jobMeta": "{{type}} auf {{backend}}, gestartet {{started}}",
+ "quantize.failed": "Quantisierung fehlgeschlagen",
+ "quantize.finished": "Quantisierung abgeschlossen: {{type}}.",
+ "quantize.stopped": "Der Job wurde gestoppt.",
+ "quantize.tryText": "Öffnen Sie die Einrichtung erneut, ändern Sie, worauf die Meldung hinweist, und starten Sie einen neuen Job. Ein neueres Backend unterstützt vielleicht eine Architektur, die ein älteres nicht unterstützt.",
+ "quantize.outputTitle": "Das Ergebnis",
+ "quantize.outputText": "Eine GGUF-Datei mit {{type}}. Importieren Sie sie, um sie in LocalAI zu nutzen, oder laden Sie sie herunter.",
+ "quantize.file": "Datei",
+ "quantize.importName": "Modellname",
+ "quantize.importNamePlaceholder": "Wird erzeugt, wenn leer",
+ "quantize.importBlocked": "Der Import ist auf diesem Server ausgeschaltet, weil er den Modellordner nicht mit dem Add-on-Host teilt.",
+ "quantize.import": "In LocalAI importieren",
+ "quantize.importing": "Wird importiert...",
+ "quantize.importFailed": "Import fehlgeschlagen",
+ "quantize.importFailedWith": "Import fehlgeschlagen: {{message}}",
+ "quantize.download": "GGUF herunterladen",
+ "quantize.nextTitle": "{{name}} ist jetzt ein Modell",
+ "quantize.nextText": "Es ist bei LocalAI registriert. Öffnen Sie einen Chat damit oder finden Sie es unter Modelle.",
+ "quantize.chat": "Mit {{name}} chatten",
+ "quantize.models": "Unter Modelle öffnen",
+ "quantize.imported": "importiert als {{name}}",
+ "quantize.emptyTitle": "Noch keine Quantisierungs-Jobs",
+ "quantize.emptyText": "Richten Sie oben einen ein. Er erscheint hier mit seinem Status.",
+ "quantize.deleteDialogText": "Dies löscht den Job mit seinen umgewandelten Dateien. Das kann nicht rückgängig gemacht werden.",
+ "quantize.startFailed": "Job konnte nicht gestartet werden",
+ "quantize.stopFailed": "Job konnte nicht gestoppt werden",
+ "quantize.deleteFailed": "Job konnte nicht gelöscht werden",
+ "quantize.status.queued": "In der Warteschlange",
+ "quantize.status.downloading": "Wird heruntergeladen",
+ "quantize.status.converting": "Wird umgewandelt",
+ "quantize.status.quantizing": "Wird quantisiert",
+ "quantize.status.completed": "Abgeschlossen",
+ "quantize.status.failed": "Fehlgeschlagen",
+ "quantize.status.stopped": "Gestoppt",
+ "off.badge": "Für Sie nicht verfügbar",
+ "off.fineTune.title": "Ihr Konto kann keine Feinabstimmung",
+ "off.fineTune.text": "Feinabstimmung ist eine eigene Berechtigung, und dieses Konto hat sie nicht.",
+ "off.quantize.title": "Ihr Konto kann nicht quantisieren",
+ "off.quantize.text": "Quantisierung ist eine eigene Berechtigung, und dieses Konto hat sie nicht.",
+ "off.admin": "Ein Administrator kann sie unter Benutzer und Schlüssel aktivieren.",
+ "off.askAdmin": "Bitten Sie einen Administrator, sie für Ihr Konto zu aktivieren.",
+ "off.back": "Zur Startseite",
+ "run.unknownPercent": "Fortschritt noch nicht gemeldet"
+}
diff --git a/addons/quantization/ui/src/i18n/en.js b/addons/quantization/ui/src/i18n/quantization.en.json
similarity index 98%
rename from addons/quantization/ui/src/i18n/en.js
rename to addons/quantization/ui/src/i18n/quantization.en.json
index f089fe8a94..ae9c4b15f5 100644
--- a/addons/quantization/ui/src/i18n/en.js
+++ b/addons/quantization/ui/src/i18n/quantization.en.json
@@ -1,6 +1,4 @@
-// Generated once from the host locale file public/locales/en/tools.json; edit by hand from here on.
-// The last key belongs to this add-on.
-export default {
+{
"steps.label": "Progress",
"steps.setup": "Set up",
"steps.check": "Check",
diff --git a/addons/quantization/ui/src/i18n/quantization.es.json b/addons/quantization/ui/src/i18n/quantization.es.json
new file mode 100644
index 0000000000..99cb147e37
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.es.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Progreso",
+ "steps.setup": "Configurar",
+ "steps.check": "Comprobar",
+ "steps.run": "Ejecutar",
+ "steps.result": "Resultado",
+ "run.stop": "Detener",
+ "run.connecting": "Conectando con el job. Esperando el primer evento de progreso.",
+ "run.progress": "Progreso",
+ "run.stages": "Etapas",
+ "run.step": "paso",
+ "run.of": "de",
+ "run.epoch": "época",
+ "run.tokens": "tokens/s",
+ "run.eta": "faltan unos {{time}}",
+ "run.etaSeconds": "faltan unos {{seconds}} s",
+ "landing.experimental": "Experimental",
+ "checks.heading": "Comprueba antes de empezar",
+ "checks.live": "Se actualiza mientras cambias la configuración.",
+ "checks.meterLabel": "{{used}} por ciento en uso, {{free}} libres de {{total}}",
+ "checks.openBackends": "Abrir Backends",
+ "checks.barOk": "Nada bloquea el inicio.",
+ "checks.barWarn_one": "{{count}} aviso. Aun así puedes empezar.",
+ "checks.barWarn_other": "{{count}} avisos. Aun así puedes empezar.",
+ "checks.title.model": "Modelo",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memoria",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funciones de recompensa",
+ "checks.title.resume": "Reanudar",
+ "checks.title.access": "Acceso al modelo",
+ "checks.title.type": "Tipo de cuantización",
+ "checks.title.source": "Origen",
+ "checks.title.name": "Nombre",
+ "checks.fineTune.modelOk": "Entrenando {{model}}.",
+ "checks.fineTune.modelMissing": "Escribe el modelo que quieres entrenar.",
+ "checks.fineTune.dataOk": "Los datos vienen de {{source}}.",
+ "checks.fineTune.dataMissing": "Añade un dataset: un id de Hugging Face o un archivo subido.",
+ "checks.fineTune.backendOk": "{{name}} está instalado.",
+ "checks.fineTune.backendMissing": "No hay ningún backend de ajuste instalado, así que el job podría no arrancar.",
+ "checks.fineTune.backendOther": "{{name}} no está entre los backends de ajuste instalados ({{list}}).",
+ "checks.fineTune.backendUnknown": "No se pudieron leer los backends instalados.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} libres de {{total}}. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}, acumulación de gradiente {{accum}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes, y LocalAI no lo estima antes de que el job arranque.",
+ "checks.fineTune.memoryCluster": "{{total}} en el mejor nodo ({{node}}). Tamaño de lote {{batch}}, longitud de secuencia {{seq}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes.",
+ "checks.fineTune.memoryCpu": "No se encontró ninguna GPU. El entrenamiento usa la CPU y {{free}} de {{total}} de RAM están libres. Espera que sea lento. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Esta cuenta no puede leer la memoria de la máquina. Tamaño de lote {{batch}}, longitud de secuencia {{seq}}. Cuánta memoria necesita el job depende del modelo y de estos ajustes.",
+ "checks.fineTune.disk": "{{free}} libres en el disco de modelos. Las descargas y los checkpoints necesitan espacio.",
+ "checks.fineTune.diskLow": "Solo {{free}} libres en el disco de modelos. Las descargas y los checkpoints necesitan espacio.",
+ "checks.fineTune.rewardMissing": "GRPO necesita al menos una función de recompensa. Añade una en Más opciones.",
+ "checks.fineTune.resume": "El entrenamiento continúa desde {{path}}.",
+ "checks.fineTune.accessNone": "No hay token definido. Un modelo restringido necesita un token de Hugging Face en Más opciones.",
+ "checks.fineTune.accessSet": "Hay un token de Hugging Face definido.",
+ "checks.quantize.modelOk": "Cuantizando {{model}}.",
+ "checks.quantize.modelMissing": "Escribe el modelo que quieres cuantizar.",
+ "checks.quantize.typeMissing": "Escribe el tipo de cuantización personalizado.",
+ "checks.quantize.backendOk": "{{name}} está instalado.",
+ "checks.quantize.backendMissing": "No hay ningún backend de cuantización instalado, así que el job podría no arrancar.",
+ "checks.quantize.backendOther": "{{name}} no está entre los backends de cuantización instalados ({{list}}).",
+ "checks.quantize.backendUnknown": "No se pudieron leer los backends instalados.",
+ "checks.quantize.memory": "{{free}} libres de {{total}} de RAM. La conversión se ejecuta en la CPU.",
+ "checks.quantize.disk": "{{free}} libres en el disco de modelos. La descarga, el archivo convertido y el resultado se escriben en disco.",
+ "checks.quantize.diskLow": "Solo {{free}} libres en el disco de modelos. La descarga, el archivo convertido y el resultado se escriben en disco.",
+ "checks.quantize.accessNone": "No hay token definido. Un modelo restringido necesita un token de Hugging Face en Más opciones.",
+ "checks.quantize.accessSet": "Hay un token de Hugging Face definido.",
+ "checks.import.sourceOk_huggingface": "Se reconoce como repositorio de Hugging Face. LocalAI revisa sus archivos al importar.",
+ "checks.import.sourceOk_url": "Formato de URL reconocido. Su contenido no se ha comprobado.",
+ "checks.import.sourceOk_config": "Se reconoce como archivo de configuración de un modelo. Se lee y se usa tal como está.",
+ "checks.import.sourceOk_oci": "Se reconoce como referencia de imagen OCI.",
+ "checks.import.sourceOk_ollama": "Se reconoce como referencia de modelo de Ollama.",
+ "checks.import.sourceOk_local": "Se reconoce como ruta en la máquina que ejecuta LocalAI.",
+ "checks.import.sourceOk": "El origen se reconoce.",
+ "checks.import.sourceUnknown": "Esto no parece ninguna forma que LocalAI conozca. Lo intentará igualmente y podría fallar.",
+ "checks.import.backendDownload": "{{name}} aún no está instalado. LocalAI lo descarga primero.",
+ "checks.import.backendChosen": "{{name}} está elegido e instalado.",
+ "checks.import.backendAuto": "LocalAI elige el backend según los archivos del origen cuando empieza la importación.",
+ "checks.import.nameTaken": "Ya hay instalado un modelo llamado {{name}}.",
+ "checks.import.nameFree": "{{name}} no está en uso.",
+ "checks.import.nameAuto": "El modelo toma el nombre del origen.",
+ "checks.import.disk": "{{free}} libres en el disco de modelos. El tamaño de la descarga se conoce cuando empieza la importación.",
+ "checks.import.memory_gpu": "Ahora hay {{free}} libres de memoria de GPU. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory_ram": "Ahora hay {{free}} libres de RAM. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory_cluster": "Hay {{free}} disponibles en el mejor nodo. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "checks.import.memory": "Ahora hay {{free}} libres de memoria. Lo que necesita este modelo se conoce cuando empieza la importación.",
+ "form.more": "Más opciones",
+ "form.backend": "Backend",
+ "form.token": "Token de Hugging Face",
+ "fineTune.starting": "Iniciando...",
+ "fineTune.noMessage": "El backend no dio ningún mensaje.",
+ "fineTune.tryTitle": "Qué hacer después",
+ "fineTune.reuse": "Volver a abrir la configuración",
+ "stop.cancelDelete": "Cancelar",
+ "log.title": "Registro",
+ "log.note": "Lo que el job ha informado desde que se abrió esta página.",
+ "log.only": "Solo avisos y errores",
+ "log.copy": "Copiar",
+ "log.copied": "Copiado",
+ "log.download": "Descargar",
+ "log.empty": "Aún no se ha informado de nada.",
+ "log.noneFiltered": "No hay avisos ni errores.",
+ "jobs.title": "Jobs anteriores",
+ "jobs.count_one": "{{count}} guardado en este servidor",
+ "jobs.count_other": "{{count}} guardados en este servidor",
+ "jobs.loading": "Cargando jobs",
+ "jobs.emptyTitle": "Aún no hay jobs de ajuste fino",
+ "jobs.emptyText": "Configura uno arriba. Aparece aquí con su estado, y puedes reutilizar su configuración más adelante.",
+ "jobs.model": "Modelo",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Estado",
+ "jobs.started": "Iniciado",
+ "jobs.actions": "Acciones",
+ "jobs.open": "Abrir este job",
+ "jobs.reuse": "Reutilizar",
+ "jobs.reuseTitle": "Usar la configuración de este job para uno nuevo",
+ "jobs.delete": "Eliminar",
+ "jobs.deleteTitle": "Eliminar este job y sus datos",
+ "jobs.deleteDialogTitle": "¿Eliminar este job?",
+ "jobs.deleteDialogText": "Esto elimina el job con sus checkpoints y cualquier modelo exportado. No se puede deshacer.",
+ "quantize.title": "Cuantizar",
+ "quantize.lede": "Haz un modelo más pequeño para que quepa en menos memoria. El resultado es un archivo GGUF que puedes importar.",
+ "quantize.newJob": "Nuevo job",
+ "quantize.sectionModel": "Modelo",
+ "quantize.model": "Modelo",
+ "quantize.modelHint": "Un id de Hugging Face, o una ruta en este host.",
+ "quantize.sectionPrecision": "Precisión",
+ "quantize.type": "Tipo de cuantización",
+ "quantize.typeHint": "Un número más bajo significa un archivo más pequeño y algo menos de calidad.",
+ "quantize.custom": "Personalizado...",
+ "quantize.customLabel": "Tipo personalizado",
+ "quantize.customPlaceholder": "por ejemplo iq4_nl",
+ "quantize.moreHint": "backend, token de Hugging Face",
+ "quantize.tokenHint": "Solo hace falta para los modelos restringidos.",
+ "quantize.recipe": "Convertir {{model}} a GGUF en {{type}}.",
+ "quantize.recipeEmpty": "Escribe un modelo para ver qué se creará.",
+ "quantize.start": "Cuantizar ({{type}})",
+ "quantize.barNote": "No se cambia nada hasta que inicies.",
+ "quantize.jobMeta": "{{type}} en {{backend}}, iniciado {{started}}",
+ "quantize.failed": "La cuantización falló",
+ "quantize.finished": "Cuantización terminada: {{type}}.",
+ "quantize.stopped": "El job se detuvo.",
+ "quantize.tryText": "Vuelve a abrir la configuración, cambia lo que indica el mensaje e inicia un job nuevo. Un backend más reciente puede soportar una arquitectura que uno anterior no soporta.",
+ "quantize.outputTitle": "El resultado",
+ "quantize.outputText": "Un archivo GGUF en {{type}}. Impórtalo para usarlo en LocalAI, o descárgalo.",
+ "quantize.file": "Archivo",
+ "quantize.importName": "Nombre del modelo",
+ "quantize.importNamePlaceholder": "Se genera si está vacío",
+ "quantize.importBlocked": "La importación está desactivada en este servidor porque no comparte la carpeta de modelos con el host del complemento.",
+ "quantize.import": "Importar a LocalAI",
+ "quantize.importing": "Importando...",
+ "quantize.importFailed": "La importación falló",
+ "quantize.importFailedWith": "La importación falló: {{message}}",
+ "quantize.download": "Descargar GGUF",
+ "quantize.nextTitle": "{{name}} ya es un modelo",
+ "quantize.nextText": "Está registrado en LocalAI. Abre un chat con él o búscalo en Modelos.",
+ "quantize.chat": "Chatear con {{name}}",
+ "quantize.models": "Abrir en Modelos",
+ "quantize.imported": "importado como {{name}}",
+ "quantize.emptyTitle": "Aún no hay jobs de cuantización",
+ "quantize.emptyText": "Configura uno arriba. Aparece aquí con su estado.",
+ "quantize.deleteDialogText": "Esto elimina el job con sus archivos convertidos. No se puede deshacer.",
+ "quantize.startFailed": "No se pudo iniciar el job",
+ "quantize.stopFailed": "No se pudo detener el job",
+ "quantize.deleteFailed": "No se pudo eliminar el job",
+ "quantize.status.queued": "En cola",
+ "quantize.status.downloading": "Descargando",
+ "quantize.status.converting": "Convirtiendo",
+ "quantize.status.quantizing": "Cuantizando",
+ "quantize.status.completed": "Completado",
+ "quantize.status.failed": "Fallido",
+ "quantize.status.stopped": "Detenido",
+ "off.badge": "No disponible para ti",
+ "off.fineTune.title": "Tu cuenta no puede hacer ajuste fino",
+ "off.fineTune.text": "El ajuste fino es un permiso aparte, y esta cuenta no lo tiene.",
+ "off.quantize.title": "Tu cuenta no puede cuantizar",
+ "off.quantize.text": "La cuantización es un permiso aparte, y esta cuenta no lo tiene.",
+ "off.admin": "Un administrador puede activarlo en Usuarios y claves.",
+ "off.askAdmin": "Pide a un administrador que lo active para tu cuenta.",
+ "off.back": "Ir al inicio",
+ "run.unknownPercent": "Progreso aún no informado"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.fr.json b/addons/quantization/ui/src/i18n/quantization.fr.json
new file mode 100644
index 0000000000..eb8bd9fe28
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.fr.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Progression",
+ "steps.setup": "Configuration",
+ "steps.check": "Vérification",
+ "steps.run": "Exécution",
+ "steps.result": "Résultat",
+ "run.stop": "Arrêter",
+ "run.connecting": "Connexion au job. En attente du premier événement de progression.",
+ "run.progress": "Progression",
+ "run.stages": "Étapes",
+ "run.step": "étape",
+ "run.of": "sur",
+ "run.epoch": "époque",
+ "run.tokens": "jetons/s",
+ "run.eta": "environ {{time}} restantes",
+ "run.etaSeconds": "environ {{seconds}} s restantes",
+ "landing.experimental": "Expérimental",
+ "checks.heading": "Vérifier avant de commencer",
+ "checks.live": "Se met à jour à mesure que vous modifiez la configuration.",
+ "checks.meterLabel": "{{used}} pour cent utilisés, {{free}} libres sur {{total}}",
+ "checks.openBackends": "Ouvrir Backends",
+ "checks.barOk": "Rien ne bloque le démarrage.",
+ "checks.barWarn_one": "{{count}} avertissement. Vous pouvez tout de même démarrer.",
+ "checks.barWarn_other": "{{count}} avertissements. Vous pouvez tout de même démarrer.",
+ "checks.title.model": "Modèle",
+ "checks.title.data": "Jeu de données",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Mémoire",
+ "checks.title.disk": "Disque",
+ "checks.title.reward": "Fonctions de récompense",
+ "checks.title.resume": "Reprise",
+ "checks.title.access": "Accès au modèle",
+ "checks.title.type": "Type de quantification",
+ "checks.title.source": "Source",
+ "checks.title.name": "Nom",
+ "checks.fineTune.modelOk": "Entraînement de {{model}}.",
+ "checks.fineTune.modelMissing": "Saisissez le modèle à entraîner.",
+ "checks.fineTune.dataOk": "Les données viennent de {{source}}.",
+ "checks.fineTune.dataMissing": "Ajoutez un jeu de données : un identifiant Hugging Face ou un fichier envoyé.",
+ "checks.fineTune.backendOk": "{{name}} est installé.",
+ "checks.fineTune.backendMissing": "Aucun backend de fine-tuning n'est installé, la tâche risque donc de ne pas démarrer.",
+ "checks.fineTune.backendOther": "{{name}} ne fait pas partie des backends de fine-tuning installés ({{list}}).",
+ "checks.fineTune.backendUnknown": "Impossible de lire les backends installés.",
+ "checks.fineTune.memoryGpu": "{{name}} : {{free}} libres sur {{total}}. Taille de batch {{batch}}, longueur de séquence {{seq}}, accumulation de gradient {{accum}}. La mémoire nécessaire dépend du modèle et de ces réglages, et LocalAI ne l'estime pas avant le démarrage de la tâche.",
+ "checks.fineTune.memoryCluster": "{{total}} sur le meilleur nœud ({{node}}). Taille de batch {{batch}}, longueur de séquence {{seq}}. La mémoire nécessaire dépend du modèle et de ces réglages.",
+ "checks.fineTune.memoryCpu": "Aucun GPU trouvé. L'entraînement utilise le CPU, et {{free}} de RAM sur {{total}} sont libres. Attendez-vous à de la lenteur. Taille de batch {{batch}}, longueur de séquence {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Ce compte ne peut pas lire la mémoire de la machine. Taille de batch {{batch}}, longueur de séquence {{seq}}. La mémoire nécessaire dépend du modèle et de ces réglages.",
+ "checks.fineTune.disk": "{{free}} libres sur le disque des modèles. Les téléchargements et les checkpoints ont besoin d'espace.",
+ "checks.fineTune.diskLow": "Seulement {{free}} libres sur le disque des modèles. Les téléchargements et les checkpoints ont besoin d'espace.",
+ "checks.fineTune.rewardMissing": "GRPO nécessite au moins une fonction de récompense. Ajoutez-en une dans Plus d'options.",
+ "checks.fineTune.resume": "L'entraînement reprend depuis {{path}}.",
+ "checks.fineTune.accessNone": "Aucun jeton défini. Un modèle protégé nécessite un jeton Hugging Face dans Plus d'options.",
+ "checks.fineTune.accessSet": "Un jeton Hugging Face est défini.",
+ "checks.quantize.modelOk": "Quantification de {{model}}.",
+ "checks.quantize.modelMissing": "Saisissez le modèle à quantifier.",
+ "checks.quantize.typeMissing": "Saisissez le type de quantification personnalisé.",
+ "checks.quantize.backendOk": "{{name}} est installé.",
+ "checks.quantize.backendMissing": "Aucun backend de quantification n'est installé, la tâche risque donc de ne pas démarrer.",
+ "checks.quantize.backendOther": "{{name}} ne fait pas partie des backends de quantification installés ({{list}}).",
+ "checks.quantize.backendUnknown": "Impossible de lire les backends installés.",
+ "checks.quantize.memory": "{{free}} de RAM sur {{total}} sont libres. La conversion se fait sur le CPU.",
+ "checks.quantize.disk": "{{free}} libres sur le disque des modèles. Le téléchargement, le fichier converti et le résultat sont écrits sur le disque.",
+ "checks.quantize.diskLow": "Seulement {{free}} libres sur le disque des modèles. Le téléchargement, le fichier converti et le résultat sont écrits sur le disque.",
+ "checks.quantize.accessNone": "Aucun jeton défini. Un modèle protégé nécessite un jeton Hugging Face dans Plus d'options.",
+ "checks.quantize.accessSet": "Un jeton Hugging Face est défini.",
+ "checks.import.sourceOk_huggingface": "Reconnu comme dépôt Hugging Face. LocalAI examine ses fichiers lors de l'import.",
+ "checks.import.sourceOk_url": "Format d'URL reconnu. Son contenu n'a pas été vérifié.",
+ "checks.import.sourceOk_config": "Reconnu comme fichier de configuration de modèle. Il est lu et utilisé tel quel.",
+ "checks.import.sourceOk_oci": "Reconnu comme référence d'image OCI.",
+ "checks.import.sourceOk_ollama": "Reconnu comme référence de modèle Ollama.",
+ "checks.import.sourceOk_local": "Reconnu comme chemin sur la machine qui exécute LocalAI.",
+ "checks.import.sourceOk": "La source est reconnue.",
+ "checks.import.sourceUnknown": "Ce format ne correspond à aucun format connu de LocalAI. L'import essaiera quand même, et peut échouer.",
+ "checks.import.backendDownload": "{{name}} n'est pas encore installé. LocalAI le télécharge d'abord.",
+ "checks.import.backendChosen": "{{name}} est choisi et installé.",
+ "checks.import.backendAuto": "LocalAI choisit le backend d'après les fichiers de la source au démarrage de l'import.",
+ "checks.import.nameTaken": "Un modèle nommé {{name}} est déjà installé.",
+ "checks.import.nameFree": "{{name}} n'est pas utilisé.",
+ "checks.import.nameAuto": "Le modèle prend le nom de la source.",
+ "checks.import.disk": "{{free}} libres sur le disque des modèles. La taille du téléchargement est connue au démarrage de l'import.",
+ "checks.import.memory_gpu": "{{free}} de mémoire GPU sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory_ram": "{{free}} de RAM sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory_cluster": "{{free}} sont disponibles sur le meilleur nœud. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "checks.import.memory": "{{free}} de mémoire sont libres maintenant. Ce dont ce modèle a besoin est connu au démarrage de l'import.",
+ "form.more": "Plus d'options",
+ "form.backend": "Backend",
+ "form.token": "Jeton Hugging Face",
+ "fineTune.starting": "Démarrage...",
+ "fineTune.noMessage": "Le backend n'a donné aucun message.",
+ "fineTune.tryTitle": "Que faire ensuite",
+ "fineTune.reuse": "Rouvrir la configuration",
+ "stop.cancelDelete": "Annuler",
+ "log.title": "Journal",
+ "log.note": "Ce que la tâche a signalé depuis l'ouverture de cette page.",
+ "log.only": "Avertissements et erreurs uniquement",
+ "log.copy": "Copier",
+ "log.copied": "Copié",
+ "log.download": "Télécharger",
+ "log.empty": "Rien n'a encore été signalé.",
+ "log.noneFiltered": "Aucun avertissement ni erreur.",
+ "jobs.title": "Tâches précédentes",
+ "jobs.count_one": "{{count}} conservée sur ce serveur",
+ "jobs.count_other": "{{count}} conservées sur ce serveur",
+ "jobs.loading": "Chargement des tâches",
+ "jobs.emptyTitle": "Aucune tâche de fine-tuning pour le moment",
+ "jobs.emptyText": "Configurez-en une ci-dessus. Elle apparaît ici avec son état, et vous pourrez réutiliser sa configuration plus tard.",
+ "jobs.model": "Modèle",
+ "jobs.kind": "Type",
+ "jobs.status": "État",
+ "jobs.started": "Démarré",
+ "jobs.actions": "Actions",
+ "jobs.open": "Ouvrir cette tâche",
+ "jobs.reuse": "Réutiliser",
+ "jobs.reuseTitle": "Utiliser la configuration de cette tâche pour une nouvelle tâche",
+ "jobs.delete": "Supprimer",
+ "jobs.deleteTitle": "Supprimer cette tâche et ses données",
+ "jobs.deleteDialogTitle": "Supprimer cette tâche ?",
+ "jobs.deleteDialogText": "Cela supprime la tâche avec ses checkpoints et tout modèle exporté. Action irréversible.",
+ "quantize.title": "Quantifier",
+ "quantize.lede": "Réduisez la taille d'un modèle pour qu'il tienne dans moins de mémoire. Le résultat est un fichier GGUF que vous pouvez importer.",
+ "quantize.newJob": "Nouvelle tâche",
+ "quantize.sectionModel": "Modèle",
+ "quantize.model": "Modèle",
+ "quantize.modelHint": "Un identifiant Hugging Face, ou un chemin sur cette machine.",
+ "quantize.sectionPrecision": "Précision",
+ "quantize.type": "Type de quantification",
+ "quantize.typeHint": "Un nombre plus petit signifie un fichier plus petit et une qualité un peu moindre.",
+ "quantize.custom": "Personnalisé...",
+ "quantize.customLabel": "Type personnalisé",
+ "quantize.customPlaceholder": "par exemple iq4_nl",
+ "quantize.moreHint": "backend, jeton Hugging Face",
+ "quantize.tokenHint": "Nécessaire uniquement pour les modèles protégés.",
+ "quantize.recipe": "Convertir {{model}} en GGUF en {{type}}.",
+ "quantize.recipeEmpty": "Saisissez un modèle pour voir ce qui sera créé.",
+ "quantize.start": "Quantifier ({{type}})",
+ "quantize.barNote": "Rien n'est modifié tant que vous ne lancez pas.",
+ "quantize.jobMeta": "{{type}} sur {{backend}}, démarré {{started}}",
+ "quantize.failed": "Échec de la quantification",
+ "quantize.finished": "Quantification terminée : {{type}}.",
+ "quantize.stopped": "La tâche a été arrêtée.",
+ "quantize.tryText": "Rouvrez la configuration, modifiez ce que le message indique et lancez une nouvelle tâche. Un backend plus récent peut prendre en charge une architecture que l'ancien ne gère pas.",
+ "quantize.outputTitle": "Le résultat",
+ "quantize.outputText": "Un fichier GGUF en {{type}}. Importez-le pour l'utiliser dans LocalAI, ou téléchargez-le.",
+ "quantize.file": "Fichier",
+ "quantize.importName": "Nom du modèle",
+ "quantize.importNamePlaceholder": "Généré si vide",
+ "quantize.importBlocked": "L'import est désactivé sur ce serveur, car il ne partage pas le dossier des modèles avec l'hôte de l'extension.",
+ "quantize.import": "Importer dans LocalAI",
+ "quantize.importing": "Importation...",
+ "quantize.importFailed": "Échec de l'importation",
+ "quantize.importFailedWith": "Échec de l'importation : {{message}}",
+ "quantize.download": "Télécharger le GGUF",
+ "quantize.nextTitle": "{{name}} est maintenant un modèle",
+ "quantize.nextText": "Il est enregistré dans LocalAI. Ouvrez une discussion avec lui, ou retrouvez-le dans Modèles.",
+ "quantize.chat": "Discuter avec {{name}}",
+ "quantize.models": "Ouvrir dans Modèles",
+ "quantize.imported": "importé sous le nom {{name}}",
+ "quantize.emptyTitle": "Aucune tâche de quantification pour le moment",
+ "quantize.emptyText": "Configurez-en une ci-dessus. Elle apparaît ici avec son état.",
+ "quantize.deleteDialogText": "Cela supprime la tâche avec ses fichiers convertis. Action irréversible.",
+ "quantize.startFailed": "Échec du lancement de la tâche",
+ "quantize.stopFailed": "Échec de l'arrêt de la tâche",
+ "quantize.deleteFailed": "Échec de la suppression de la tâche",
+ "quantize.status.queued": "En file d'attente",
+ "quantize.status.downloading": "Téléchargement",
+ "quantize.status.converting": "Conversion",
+ "quantize.status.quantizing": "Quantification",
+ "quantize.status.completed": "Terminée",
+ "quantize.status.failed": "Échouée",
+ "quantize.status.stopped": "Arrêtée",
+ "off.badge": "Non disponible pour vous",
+ "off.fineTune.title": "Votre compte ne peut pas faire de fine-tuning",
+ "off.fineTune.text": "Le fine-tuning est une autorisation distincte, que ce compte n'a pas.",
+ "off.quantize.title": "Votre compte ne peut pas quantifier",
+ "off.quantize.text": "La quantification est une autorisation distincte, que ce compte n'a pas.",
+ "off.admin": "Un administrateur peut l'activer dans Utilisateurs et clés.",
+ "off.askAdmin": "Demandez à un administrateur de l'activer pour votre compte.",
+ "off.back": "Aller à l'accueil",
+ "run.unknownPercent": "Progression pas encore communiquée"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.id.json b/addons/quantization/ui/src/i18n/quantization.id.json
new file mode 100644
index 0000000000..9345b2638d
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.id.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Progres",
+ "steps.setup": "Siapkan",
+ "steps.check": "Periksa",
+ "steps.run": "Jalankan",
+ "steps.result": "Hasil",
+ "run.stop": "Hentikan",
+ "run.connecting": "Menghubungkan ke job. Menunggu event progres pertama.",
+ "run.progress": "Progres",
+ "run.stages": "Tahap",
+ "run.step": "langkah",
+ "run.of": "dari",
+ "run.epoch": "epoch",
+ "run.tokens": "token/s",
+ "run.eta": "sekitar {{time}} lagi",
+ "run.etaSeconds": "sekitar {{seconds}} d lagi",
+ "landing.experimental": "Eksperimental",
+ "checks.heading": "Periksa sebelum memulai",
+ "checks.live": "Diperbarui saat Anda mengubah pengaturan.",
+ "checks.meterLabel": "{{used}} persen terpakai, {{free}} tersedia dari {{total}}",
+ "checks.openBackends": "Buka Backend",
+ "checks.barOk": "Tidak ada yang menghalangi permulaan.",
+ "checks.barWarn_one": "{{count}} peringatan. Anda tetap bisa memulai.",
+ "checks.barWarn_other": "{{count}} peringatan. Anda tetap bisa memulai.",
+ "checks.title.model": "Model",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memori",
+ "checks.title.disk": "Disk",
+ "checks.title.reward": "Fungsi reward",
+ "checks.title.resume": "Melanjutkan",
+ "checks.title.access": "Akses model",
+ "checks.title.type": "Tipe kuantisasi",
+ "checks.title.source": "Sumber",
+ "checks.title.name": "Nama",
+ "checks.fineTune.modelOk": "Melatih {{model}}.",
+ "checks.fineTune.modelMissing": "Masukkan model yang akan dilatih.",
+ "checks.fineTune.dataOk": "Data berasal dari {{source}}.",
+ "checks.fineTune.dataMissing": "Tambahkan dataset: id Hugging Face atau file yang diunggah.",
+ "checks.fineTune.backendOk": "{{name}} sudah terpasang.",
+ "checks.fineTune.backendMissing": "Belum ada backend fine-tuning yang terpasang, jadi job mungkin gagal dimulai.",
+ "checks.fineTune.backendOther": "{{name}} tidak termasuk backend fine-tuning yang terpasang ({{list}}).",
+ "checks.fineTune.backendUnknown": "Backend yang terpasang tidak dapat dibaca.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} dari {{total}} tersedia. Ukuran batch {{batch}}, panjang sekuens {{seq}}, akumulasi gradien {{accum}}. Kebutuhan job bergantung pada model dan pengaturan ini, dan LocalAI tidak memperkirakannya sebelum job dimulai.",
+ "checks.fineTune.memoryCluster": "{{total}} di node terbaik ({{node}}). Ukuran batch {{batch}}, panjang sekuens {{seq}}. Kebutuhan job bergantung pada model dan pengaturan ini.",
+ "checks.fineTune.memoryCpu": "GPU tidak ditemukan. Pelatihan menggunakan CPU dan {{free}} dari {{total}} RAM tersedia. Diperkirakan lambat. Ukuran batch {{batch}}, panjang sekuens {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Akun ini tidak dapat membaca memori mesin. Ukuran batch {{batch}}, panjang sekuens {{seq}}. Kebutuhan job bergantung pada model dan pengaturan ini.",
+ "checks.fineTune.disk": "{{free}} tersedia di disk model. Unduhan dan checkpoint membutuhkan ruang.",
+ "checks.fineTune.diskLow": "Hanya {{free}} tersedia di disk model. Unduhan dan checkpoint membutuhkan ruang.",
+ "checks.fineTune.rewardMissing": "GRPO membutuhkan minimal satu fungsi reward. Tambahkan satu di Opsi lainnya.",
+ "checks.fineTune.resume": "Pelatihan dilanjutkan dari {{path}}.",
+ "checks.fineTune.accessNone": "Token belum diatur. Model yang dibatasi membutuhkan token Hugging Face di Opsi lainnya.",
+ "checks.fineTune.accessSet": "Token Hugging Face sudah diatur.",
+ "checks.quantize.modelOk": "Mengkuantisasi {{model}}.",
+ "checks.quantize.modelMissing": "Masukkan model yang akan dikuantisasi.",
+ "checks.quantize.typeMissing": "Masukkan tipe kuantisasi kustom.",
+ "checks.quantize.backendOk": "{{name}} sudah terpasang.",
+ "checks.quantize.backendMissing": "Belum ada backend kuantisasi yang terpasang, jadi job mungkin gagal dimulai.",
+ "checks.quantize.backendOther": "{{name}} tidak termasuk backend kuantisasi yang terpasang ({{list}}).",
+ "checks.quantize.backendUnknown": "Backend yang terpasang tidak dapat dibaca.",
+ "checks.quantize.memory": "{{free}} dari {{total}} RAM tersedia. Konversi berjalan di CPU.",
+ "checks.quantize.disk": "{{free}} tersedia di disk model. Unduhan, file hasil konversi, dan hasilnya ditulis ke disk.",
+ "checks.quantize.diskLow": "Hanya {{free}} tersedia di disk model. Unduhan, file hasil konversi, dan hasilnya ditulis ke disk.",
+ "checks.quantize.accessNone": "Token belum diatur. Model yang dibatasi membutuhkan token Hugging Face di Opsi lainnya.",
+ "checks.quantize.accessSet": "Token Hugging Face sudah diatur.",
+ "checks.import.sourceOk_huggingface": "Dikenali sebagai repositori Hugging Face. LocalAI memeriksa file-filenya saat Anda mengimpor.",
+ "checks.import.sourceOk_url": "Format URL dikenali. Isinya belum diperiksa.",
+ "checks.import.sourceOk_config": "Dikenali sebagai file konfigurasi model. File dibaca dan digunakan apa adanya.",
+ "checks.import.sourceOk_oci": "Dikenali sebagai referensi image OCI.",
+ "checks.import.sourceOk_ollama": "Dikenali sebagai referensi model Ollama.",
+ "checks.import.sourceOk_local": "Dikenali sebagai path di mesin yang menjalankan LocalAI.",
+ "checks.import.sourceOk": "Sumber dikenali.",
+ "checks.import.sourceUnknown": "Ini tidak tampak seperti format yang diketahui LocalAI. Tetap akan dicoba, dan mungkin gagal.",
+ "checks.import.backendDownload": "{{name}} belum terpasang. LocalAI mengunduhnya terlebih dahulu.",
+ "checks.import.backendChosen": "{{name}} dipilih dan terpasang.",
+ "checks.import.backendAuto": "LocalAI memilih backend dari file sumber saat impor dimulai.",
+ "checks.import.nameTaken": "Model bernama {{name}} sudah terpasang.",
+ "checks.import.nameFree": "{{name}} belum digunakan.",
+ "checks.import.nameAuto": "Model dinamai sesuai sumbernya.",
+ "checks.import.disk": "{{free}} tersedia di disk model. Ukuran unduhan diketahui saat impor dimulai.",
+ "checks.import.memory_gpu": "{{free}} memori GPU tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory_ram": "{{free}} RAM tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory_cluster": "{{free}} tersedia di node terbaik. Kebutuhan model ini diketahui saat impor dimulai.",
+ "checks.import.memory": "{{free}} memori tersedia saat ini. Kebutuhan model ini diketahui saat impor dimulai.",
+ "form.more": "Opsi lainnya",
+ "form.backend": "Backend",
+ "form.token": "Token Hugging Face",
+ "fineTune.starting": "Memulai...",
+ "fineTune.noMessage": "Backend tidak memberikan pesan.",
+ "fineTune.tryTitle": "Langkah berikutnya",
+ "fineTune.reuse": "Buka pengaturan lagi",
+ "stop.cancelDelete": "Batal",
+ "log.title": "Log",
+ "log.note": "Yang dilaporkan job sejak halaman ini dibuka.",
+ "log.only": "Hanya peringatan dan error",
+ "log.copy": "Salin",
+ "log.copied": "Disalin",
+ "log.download": "Unduh",
+ "log.empty": "Belum ada yang dilaporkan.",
+ "log.noneFiltered": "Tidak ada peringatan atau error.",
+ "jobs.title": "Job sebelumnya",
+ "jobs.count_one": "{{count}} disimpan di server ini",
+ "jobs.count_other": "{{count}} disimpan di server ini",
+ "jobs.loading": "Memuat job",
+ "jobs.emptyTitle": "Belum ada job fine-tuning",
+ "jobs.emptyText": "Siapkan satu di atas. Job akan muncul di sini dengan statusnya, dan Anda dapat menggunakan kembali pengaturannya nanti.",
+ "jobs.model": "Model",
+ "jobs.kind": "Jenis",
+ "jobs.status": "Status",
+ "jobs.started": "Dimulai",
+ "jobs.actions": "Aksi",
+ "jobs.open": "Buka job ini",
+ "jobs.reuse": "Gunakan ulang",
+ "jobs.reuseTitle": "Gunakan pengaturan job ini untuk job baru",
+ "jobs.delete": "Hapus",
+ "jobs.deleteTitle": "Hapus job ini beserta datanya",
+ "jobs.deleteDialogTitle": "Hapus job ini?",
+ "jobs.deleteDialogText": "Ini menghapus job beserta checkpoint dan model hasil ekspor. Tindakan ini tidak dapat dibatalkan.",
+ "quantize.title": "Kuantisasi",
+ "quantize.lede": "Buat model lebih kecil agar muat dalam memori yang lebih sedikit. Hasilnya adalah file GGUF yang dapat Anda impor.",
+ "quantize.newJob": "Job baru",
+ "quantize.sectionModel": "Model",
+ "quantize.model": "Model",
+ "quantize.modelHint": "Id Hugging Face, atau path di host ini.",
+ "quantize.sectionPrecision": "Presisi",
+ "quantize.type": "Tipe kuantisasi",
+ "quantize.typeHint": "Angka yang lebih kecil berarti file lebih kecil dan kualitas sedikit lebih rendah.",
+ "quantize.custom": "Kustom...",
+ "quantize.customLabel": "Tipe kustom",
+ "quantize.customPlaceholder": "contoh iq4_nl",
+ "quantize.moreHint": "backend, token Hugging Face",
+ "quantize.tokenHint": "Hanya dibutuhkan untuk model yang dibatasi.",
+ "quantize.recipe": "Konversi {{model}} ke GGUF pada {{type}}.",
+ "quantize.recipeEmpty": "Masukkan model untuk melihat apa yang akan dibuat.",
+ "quantize.start": "Kuantisasi ({{type}})",
+ "quantize.barNote": "Tidak ada yang berubah sampai Anda memulai.",
+ "quantize.jobMeta": "{{type}} pada {{backend}}, dimulai {{started}}",
+ "quantize.failed": "Kuantisasi gagal",
+ "quantize.finished": "Kuantisasi selesai: {{type}}.",
+ "quantize.stopped": "Job dihentikan.",
+ "quantize.tryText": "Buka pengaturan lagi, ubah apa yang ditunjuk pesan, lalu mulai job baru. Backend yang lebih baru mungkin mendukung arsitektur yang tidak didukung backend lama.",
+ "quantize.outputTitle": "Hasil",
+ "quantize.outputText": "File GGUF pada {{type}}. Impor untuk digunakan di LocalAI, atau unduh.",
+ "quantize.file": "File",
+ "quantize.importName": "Nama model",
+ "quantize.importNamePlaceholder": "Dibuat otomatis jika kosong",
+ "quantize.importBlocked": "Impor dimatikan di server ini karena server tidak berbagi folder model dengan host add-on.",
+ "quantize.import": "Impor ke LocalAI",
+ "quantize.importing": "Mengimpor...",
+ "quantize.importFailed": "Impor gagal",
+ "quantize.importFailedWith": "Impor gagal: {{message}}",
+ "quantize.download": "Unduh GGUF",
+ "quantize.nextTitle": "{{name}} sekarang adalah sebuah model",
+ "quantize.nextText": "Model sudah terdaftar di LocalAI. Buka chat dengannya, atau temukan di Model.",
+ "quantize.chat": "Chat dengan {{name}}",
+ "quantize.models": "Buka di Model",
+ "quantize.imported": "diimpor sebagai {{name}}",
+ "quantize.emptyTitle": "Belum ada job kuantisasi",
+ "quantize.emptyText": "Siapkan satu di atas. Job akan muncul di sini dengan statusnya.",
+ "quantize.deleteDialogText": "Ini menghapus job beserta file hasil konversinya. Tindakan ini tidak dapat dibatalkan.",
+ "quantize.startFailed": "Gagal memulai job",
+ "quantize.stopFailed": "Gagal menghentikan job",
+ "quantize.deleteFailed": "Gagal menghapus job",
+ "quantize.status.queued": "Antre",
+ "quantize.status.downloading": "Mengunduh",
+ "quantize.status.converting": "Mengonversi",
+ "quantize.status.quantizing": "Mengkuantisasi",
+ "quantize.status.completed": "Selesai",
+ "quantize.status.failed": "Gagal",
+ "quantize.status.stopped": "Dihentikan",
+ "off.badge": "Tidak tersedia untuk Anda",
+ "off.fineTune.title": "Akun Anda tidak dapat melakukan fine-tuning",
+ "off.fineTune.text": "Fine-tuning adalah izin tersendiri, dan akun ini tidak memilikinya.",
+ "off.quantize.title": "Akun Anda tidak dapat melakukan kuantisasi",
+ "off.quantize.text": "Kuantisasi adalah izin tersendiri, dan akun ini tidak memilikinya.",
+ "off.admin": "Administrator dapat mengaktifkannya di Pengguna dan kunci.",
+ "off.askAdmin": "Minta administrator untuk mengaktifkannya untuk akun Anda.",
+ "off.back": "Ke Beranda",
+ "run.unknownPercent": "Kemajuan belum dilaporkan"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.it.json b/addons/quantization/ui/src/i18n/quantization.it.json
new file mode 100644
index 0000000000..4d75f46fa6
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.it.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Avanzamento",
+ "steps.setup": "Configura",
+ "steps.check": "Verifica",
+ "steps.run": "Esecuzione",
+ "steps.result": "Risultato",
+ "run.stop": "Ferma",
+ "run.connecting": "Connessione al job. In attesa del primo evento di avanzamento.",
+ "run.progress": "Avanzamento",
+ "run.stages": "Fasi",
+ "run.step": "passo",
+ "run.of": "di",
+ "run.epoch": "epoca",
+ "run.tokens": "token/s",
+ "run.eta": "circa {{time}} rimanenti",
+ "run.etaSeconds": "circa {{seconds}} s rimanenti",
+ "landing.experimental": "Sperimentale",
+ "checks.heading": "Verifica prima di iniziare",
+ "checks.live": "Si aggiorna mentre cambi la configurazione.",
+ "checks.meterLabel": "{{used}} per cento in uso, {{free}} liberi di {{total}}",
+ "checks.openBackends": "Apri Backend",
+ "checks.barOk": "Nulla blocca l'avvio.",
+ "checks.barWarn_one": "{{count}} avviso. Puoi comunque avviare.",
+ "checks.barWarn_other": "{{count}} avvisi. Puoi comunque avviare.",
+ "checks.title.model": "Modello",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memoria",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funzioni di ricompensa",
+ "checks.title.resume": "Ripresa",
+ "checks.title.access": "Accesso al modello",
+ "checks.title.type": "Tipo di quantizzazione",
+ "checks.title.source": "Sorgente",
+ "checks.title.name": "Nome",
+ "checks.fineTune.modelOk": "Addestramento di {{model}}.",
+ "checks.fineTune.modelMissing": "Inserisci il modello da addestrare.",
+ "checks.fineTune.dataOk": "I dati arrivano da {{source}}.",
+ "checks.fineTune.dataMissing": "Aggiungi un dataset: un id Hugging Face o un file caricato.",
+ "checks.fineTune.backendOk": "{{name}} è installato.",
+ "checks.fineTune.backendMissing": "Nessun backend di fine-tuning è installato, quindi il job potrebbe non partire.",
+ "checks.fineTune.backendOther": "{{name}} non è tra i backend di fine-tuning installati ({{list}}).",
+ "checks.fineTune.backendUnknown": "Impossibile leggere i backend installati.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} liberi di {{total}}. Batch size {{batch}}, lunghezza della sequenza {{seq}}, accumulo del gradiente {{accum}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni, e LocalAI non la stima prima dell'avvio del job.",
+ "checks.fineTune.memoryCluster": "{{total}} sul nodo migliore ({{node}}). Batch size {{batch}}, lunghezza della sequenza {{seq}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni.",
+ "checks.fineTune.memoryCpu": "Nessuna GPU trovata. L'addestramento usa la CPU e {{free}} di RAM su {{total}} sono liberi. Aspettati lentezza. Batch size {{batch}}, lunghezza della sequenza {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Questo account non può leggere la memoria della macchina. Batch size {{batch}}, lunghezza della sequenza {{seq}}. Quanta memoria serva al job dipende dal modello e da queste impostazioni.",
+ "checks.fineTune.disk": "{{free}} liberi sul disco dei modelli. Download e checkpoint richiedono spazio.",
+ "checks.fineTune.diskLow": "Solo {{free}} liberi sul disco dei modelli. Download e checkpoint richiedono spazio.",
+ "checks.fineTune.rewardMissing": "GRPO richiede almeno una funzione di ricompensa. Aggiungine una in Altre opzioni.",
+ "checks.fineTune.resume": "L'addestramento continua da {{path}}.",
+ "checks.fineTune.accessNone": "Nessun token impostato. Un modello con accesso limitato richiede un token Hugging Face in Altre opzioni.",
+ "checks.fineTune.accessSet": "È impostato un token Hugging Face.",
+ "checks.quantize.modelOk": "Quantizzazione di {{model}}.",
+ "checks.quantize.modelMissing": "Inserisci il modello da quantizzare.",
+ "checks.quantize.typeMissing": "Inserisci il tipo di quantizzazione personalizzato.",
+ "checks.quantize.backendOk": "{{name}} è installato.",
+ "checks.quantize.backendMissing": "Nessun backend di quantizzazione è installato, quindi il job potrebbe non partire.",
+ "checks.quantize.backendOther": "{{name}} non è tra i backend di quantizzazione installati ({{list}}).",
+ "checks.quantize.backendUnknown": "Impossibile leggere i backend installati.",
+ "checks.quantize.memory": "{{free}} di RAM su {{total}} sono liberi. La conversione gira sulla CPU.",
+ "checks.quantize.disk": "{{free}} liberi sul disco dei modelli. Download, file convertito e risultato vengono scritti su disco.",
+ "checks.quantize.diskLow": "Solo {{free}} liberi sul disco dei modelli. Download, file convertito e risultato vengono scritti su disco.",
+ "checks.quantize.accessNone": "Nessun token impostato. Un modello con accesso limitato richiede un token Hugging Face in Altre opzioni.",
+ "checks.quantize.accessSet": "È impostato un token Hugging Face.",
+ "checks.import.sourceOk_huggingface": "Riconosciuto come repository Hugging Face. LocalAI controlla i suoi file quando importi.",
+ "checks.import.sourceOk_url": "Formato URL riconosciuto. Il contenuto non è stato verificato.",
+ "checks.import.sourceOk_config": "Riconosciuto come file di configurazione del modello. Viene letto e usato così com'è.",
+ "checks.import.sourceOk_oci": "Riconosciuto come riferimento a un'immagine OCI.",
+ "checks.import.sourceOk_ollama": "Riconosciuto come riferimento a un modello Ollama.",
+ "checks.import.sourceOk_local": "Riconosciuto come percorso sulla macchina che esegue LocalAI.",
+ "checks.import.sourceOk": "La sorgente è riconosciuta.",
+ "checks.import.sourceUnknown": "Non sembra un formato noto a LocalAI. Ci proverà comunque, e potrebbe non riuscire.",
+ "checks.import.backendDownload": "{{name}} non è ancora installato. LocalAI lo scarica prima.",
+ "checks.import.backendChosen": "{{name}} è scelto e installato.",
+ "checks.import.backendAuto": "LocalAI sceglie il backend dai file della sorgente quando l'importazione inizia.",
+ "checks.import.nameTaken": "Un modello chiamato {{name}} è già installato.",
+ "checks.import.nameFree": "{{name}} non è in uso.",
+ "checks.import.nameAuto": "Il modello prende il nome dalla sorgente.",
+ "checks.import.disk": "{{free}} liberi sul disco dei modelli. La dimensione del download è nota quando l'importazione inizia.",
+ "checks.import.memory_gpu": "{{free}} di memoria GPU sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory_ram": "{{free}} di RAM sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory_cluster": "{{free}} sono disponibili sul nodo migliore. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "checks.import.memory": "{{free}} di memoria sono liberi ora. Quanta memoria serva a questo modello è noto quando l'importazione inizia.",
+ "form.more": "Altre opzioni",
+ "form.backend": "Backend",
+ "form.token": "Token Hugging Face",
+ "fineTune.starting": "Avvio in corso...",
+ "fineTune.noMessage": "Il backend non ha dato alcun messaggio.",
+ "fineTune.tryTitle": "Cosa fare dopo",
+ "fineTune.reuse": "Apri di nuovo la configurazione",
+ "stop.cancelDelete": "Annulla",
+ "log.title": "Log",
+ "log.note": "Ciò che il job ha riportato da quando è aperta questa pagina.",
+ "log.only": "Solo avvisi ed errori",
+ "log.copy": "Copia",
+ "log.copied": "Copiato",
+ "log.download": "Scarica",
+ "log.empty": "Ancora nulla da riportare.",
+ "log.noneFiltered": "Nessun avviso o errore.",
+ "jobs.title": "Job precedenti",
+ "jobs.count_one": "{{count}} conservato su questo server",
+ "jobs.count_other": "{{count}} conservati su questo server",
+ "jobs.loading": "Caricamento dei job",
+ "jobs.emptyTitle": "Ancora nessun job di fine-tuning",
+ "jobs.emptyText": "Configurane uno sopra. Compare qui con il suo stato, e in seguito puoi riusarne la configurazione.",
+ "jobs.model": "Modello",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Stato",
+ "jobs.started": "Avviato",
+ "jobs.actions": "Azioni",
+ "jobs.open": "Apri questo job",
+ "jobs.reuse": "Riusa",
+ "jobs.reuseTitle": "Usa la configurazione di questo job per un nuovo job",
+ "jobs.delete": "Elimina",
+ "jobs.deleteTitle": "Elimina questo job e i suoi dati",
+ "jobs.deleteDialogTitle": "Eliminare questo job?",
+ "jobs.deleteDialogText": "Questo elimina il job con i suoi checkpoint e l'eventuale modello esportato. Non può essere annullato.",
+ "quantize.title": "Quantizza",
+ "quantize.lede": "Rendi un modello più piccolo, così occupa meno memoria. Il risultato è un file GGUF che puoi importare.",
+ "quantize.newJob": "Nuovo job",
+ "quantize.sectionModel": "Modello",
+ "quantize.model": "Modello",
+ "quantize.modelHint": "Un id Hugging Face, o un percorso su questo host.",
+ "quantize.sectionPrecision": "Precisione",
+ "quantize.type": "Tipo di quantizzazione",
+ "quantize.typeHint": "Un numero più piccolo significa un file più piccolo e una qualità leggermente inferiore.",
+ "quantize.custom": "Personalizzato...",
+ "quantize.customLabel": "Tipo personalizzato",
+ "quantize.customPlaceholder": "per esempio iq4_nl",
+ "quantize.moreHint": "backend, token Hugging Face",
+ "quantize.tokenHint": "Serve solo per i modelli con accesso limitato.",
+ "quantize.recipe": "Converti {{model}} in GGUF a {{type}}.",
+ "quantize.recipeEmpty": "Inserisci un modello per vedere cosa verrà creato.",
+ "quantize.start": "Quantizza ({{type}})",
+ "quantize.barNote": "Non cambia nulla finché non avvii.",
+ "quantize.jobMeta": "{{type}} su {{backend}}, avviato {{started}}",
+ "quantize.failed": "Quantizzazione non riuscita",
+ "quantize.finished": "Quantizzazione completata: {{type}}.",
+ "quantize.stopped": "Il job è stato fermato.",
+ "quantize.tryText": "Apri di nuovo la configurazione, cambia ciò che indica il messaggio e avvia un nuovo job. Un backend più recente può supportare un'architettura che uno più vecchio non supporta.",
+ "quantize.outputTitle": "Il risultato",
+ "quantize.outputText": "Un file GGUF a {{type}}. Importalo per usarlo in LocalAI, oppure scaricalo.",
+ "quantize.file": "File",
+ "quantize.importName": "Nome del modello",
+ "quantize.importNamePlaceholder": "Generato se vuoto",
+ "quantize.importBlocked": "L'importazione è disattivata su questo server perché non condivide la cartella dei modelli con l'host dell'add-on.",
+ "quantize.import": "Importa in LocalAI",
+ "quantize.importing": "Importazione...",
+ "quantize.importFailed": "Importazione non riuscita",
+ "quantize.importFailedWith": "Importazione non riuscita: {{message}}",
+ "quantize.download": "Scarica GGUF",
+ "quantize.nextTitle": "{{name}} è ora un modello",
+ "quantize.nextText": "È registrato in LocalAI. Apri una chat con esso, oppure trovalo in Modelli.",
+ "quantize.chat": "Chatta con {{name}}",
+ "quantize.models": "Apri in Modelli",
+ "quantize.imported": "importato come {{name}}",
+ "quantize.emptyTitle": "Ancora nessun job di quantizzazione",
+ "quantize.emptyText": "Configurane uno sopra. Compare qui con il suo stato.",
+ "quantize.deleteDialogText": "Questo elimina il job con i file convertiti. Non può essere annullato.",
+ "quantize.startFailed": "Impossibile avviare il job",
+ "quantize.stopFailed": "Impossibile fermare il job",
+ "quantize.deleteFailed": "Impossibile eliminare il job",
+ "quantize.status.queued": "In coda",
+ "quantize.status.downloading": "Download in corso",
+ "quantize.status.converting": "Conversione",
+ "quantize.status.quantizing": "Quantizzazione",
+ "quantize.status.completed": "Completato",
+ "quantize.status.failed": "Non riuscito",
+ "quantize.status.stopped": "Fermato",
+ "off.badge": "Non disponibile per te",
+ "off.fineTune.title": "Il tuo account non può fare fine-tuning",
+ "off.fineTune.text": "Il fine-tuning è un permesso separato, e questo account non ce l'ha.",
+ "off.quantize.title": "Il tuo account non può quantizzare",
+ "off.quantize.text": "La quantizzazione è un permesso separato, e questo account non ce l'ha.",
+ "off.admin": "Un amministratore può attivarlo da Utenti e chiavi.",
+ "off.askAdmin": "Chiedi a un amministratore di attivarlo per il tuo account.",
+ "off.back": "Vai alla Home",
+ "run.unknownPercent": "Avanzamento non ancora comunicato"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.ko.json b/addons/quantization/ui/src/i18n/quantization.ko.json
new file mode 100644
index 0000000000..1e6d4e6833
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.ko.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "진행 상황",
+ "steps.setup": "설정",
+ "steps.check": "확인",
+ "steps.run": "실행",
+ "steps.result": "결과",
+ "run.stop": "중지",
+ "run.connecting": "작업에 연결하는 중입니다. 첫 진행 이벤트를 기다리고 있습니다.",
+ "run.progress": "진행률",
+ "run.stages": "단계",
+ "run.step": "스텝",
+ "run.of": "/",
+ "run.epoch": "에포크",
+ "run.tokens": "tokens/s",
+ "run.eta": "약 {{time}} 남음",
+ "run.etaSeconds": "약 {{seconds}}초 남음",
+ "landing.experimental": "실험적",
+ "checks.heading": "시작하기 전에 확인",
+ "checks.live": "설정을 바꿀 때마다 업데이트됩니다.",
+ "checks.meterLabel": "{{used}}% 사용 중, {{total}} 중 {{free}} 사용 가능",
+ "checks.openBackends": "백엔드 열기",
+ "checks.barOk": "시작을 막는 항목이 없습니다.",
+ "checks.barWarn_one": "경고 {{count}}개. 그래도 시작할 수 있습니다.",
+ "checks.barWarn_other": "경고 {{count}}개. 그래도 시작할 수 있습니다.",
+ "checks.title.model": "모델",
+ "checks.title.data": "데이터셋",
+ "checks.title.backend": "백엔드",
+ "checks.title.memory": "메모리",
+ "checks.title.disk": "디스크",
+ "checks.title.reward": "보상 함수",
+ "checks.title.resume": "재개",
+ "checks.title.access": "모델 접근",
+ "checks.title.type": "양자화 유형",
+ "checks.title.source": "소스",
+ "checks.title.name": "이름",
+ "checks.fineTune.modelOk": "{{model}} 학습 중입니다.",
+ "checks.fineTune.modelMissing": "학습할 모델을 입력하세요.",
+ "checks.fineTune.dataOk": "데이터는 {{source}}에서 옵니다.",
+ "checks.fineTune.dataMissing": "데이터셋을 추가하세요: Hugging Face id 또는 업로드한 파일.",
+ "checks.fineTune.backendOk": "{{name}}이(가) 설치되어 있습니다.",
+ "checks.fineTune.backendMissing": "파인튜닝 백엔드가 설치되어 있지 않아 작업이 시작되지 않을 수 있습니다.",
+ "checks.fineTune.backendOther": "{{name}}은(는) 설치된 파인튜닝 백엔드({{list}}) 중에 없습니다.",
+ "checks.fineTune.backendUnknown": "설치된 백엔드를 읽을 수 없습니다.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{total}} 중 {{free}} 사용 가능. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}, 그래디언트 누적 {{accum}}. 작업에 필요한 양은 모델과 이 설정에 따라 다르며, LocalAI는 작업 시작 전에 이를 추정하지 않습니다.",
+ "checks.fineTune.memoryCluster": "최적 노드({{node}})의 {{total}}. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}. 작업에 필요한 양은 모델과 이 설정에 따라 다릅니다.",
+ "checks.fineTune.memoryCpu": "GPU를 찾을 수 없습니다. 학습은 CPU를 사용하며 RAM {{total}} 중 {{free}}가 사용 가능합니다. 느릴 것으로 예상됩니다. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}.",
+ "checks.fineTune.memoryUnknown": "이 계정은 컴퓨터의 메모리를 읽을 수 없습니다. 배치 크기 {{batch}}, 시퀀스 길이 {{seq}}. 작업에 필요한 양은 모델과 이 설정에 따라 다릅니다.",
+ "checks.fineTune.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드와 체크포인트에 공간이 필요합니다.",
+ "checks.fineTune.diskLow": "모델 디스크에 {{free}}만 사용 가능. 다운로드와 체크포인트에 공간이 필요합니다.",
+ "checks.fineTune.rewardMissing": "GRPO에는 보상 함수가 최소 하나 필요합니다. 추가 옵션에서 하나를 추가하세요.",
+ "checks.fineTune.resume": "학습이 {{path}}에서 이어집니다.",
+ "checks.fineTune.accessNone": "토큰이 설정되지 않았습니다. 접근이 제한된 모델은 추가 옵션에서 Hugging Face 토큰이 필요합니다.",
+ "checks.fineTune.accessSet": "Hugging Face 토큰이 설정되어 있습니다.",
+ "checks.quantize.modelOk": "{{model}}을(를) 양자화합니다.",
+ "checks.quantize.modelMissing": "양자화할 모델을 입력하세요.",
+ "checks.quantize.typeMissing": "사용자 지정 양자화 유형을 입력하세요.",
+ "checks.quantize.backendOk": "{{name}}이(가) 설치되어 있습니다.",
+ "checks.quantize.backendMissing": "양자화 백엔드가 설치되어 있지 않아 작업이 시작되지 않을 수 있습니다.",
+ "checks.quantize.backendOther": "{{name}}은(는) 설치된 양자화 백엔드({{list}}) 중에 없습니다.",
+ "checks.quantize.backendUnknown": "설치된 백엔드를 읽을 수 없습니다.",
+ "checks.quantize.memory": "RAM {{total}} 중 {{free}}가 사용 가능합니다. 변환은 CPU에서 실행됩니다.",
+ "checks.quantize.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드, 변환된 파일, 결과가 디스크에 기록됩니다.",
+ "checks.quantize.diskLow": "모델 디스크에 {{free}}만 사용 가능. 다운로드, 변환된 파일, 결과가 디스크에 기록됩니다.",
+ "checks.quantize.accessNone": "토큰이 설정되지 않았습니다. 접근이 제한된 모델은 추가 옵션에서 Hugging Face 토큰이 필요합니다.",
+ "checks.quantize.accessSet": "Hugging Face 토큰이 설정되어 있습니다.",
+ "checks.import.sourceOk_huggingface": "Hugging Face 저장소로 인식되었습니다. 가져올 때 LocalAI가 파일을 확인합니다.",
+ "checks.import.sourceOk_url": "URL 형식이 인식되었습니다. 내용은 확인하지 않았습니다.",
+ "checks.import.sourceOk_config": "모델 구성 파일로 인식되었습니다. 읽은 뒤 있는 그대로 사용합니다.",
+ "checks.import.sourceOk_oci": "OCI 이미지 참조로 인식되었습니다.",
+ "checks.import.sourceOk_ollama": "Ollama 모델 참조로 인식되었습니다.",
+ "checks.import.sourceOk_local": "LocalAI를 실행하는 컴퓨터의 경로로 인식되었습니다.",
+ "checks.import.sourceOk": "소스가 인식되었습니다.",
+ "checks.import.sourceUnknown": "LocalAI가 아는 형식이 아닌 것 같습니다. 그래도 시도하며, 실패할 수 있습니다.",
+ "checks.import.backendDownload": "{{name}}이(가) 아직 설치되어 있지 않습니다. LocalAI가 먼저 다운로드합니다.",
+ "checks.import.backendChosen": "{{name}}이(가) 선택되어 설치되어 있습니다.",
+ "checks.import.backendAuto": "가져오기가 시작되면 LocalAI가 소스의 파일을 보고 백엔드를 고릅니다.",
+ "checks.import.nameTaken": "{{name}}이라는 모델이 이미 설치되어 있습니다.",
+ "checks.import.nameFree": "{{name}}은(는) 사용 중이 아닙니다.",
+ "checks.import.nameAuto": "모델은 소스 이름을 따라 명명됩니다.",
+ "checks.import.disk": "모델 디스크에 {{free}} 사용 가능. 다운로드 크기는 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_gpu": "지금 GPU 메모리 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_ram": "지금 RAM {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory_cluster": "최적 노드에 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "checks.import.memory": "지금 메모리 {{free}}이(가) 사용 가능합니다. 이 모델에 필요한 양은 가져오기가 시작되면 알 수 있습니다.",
+ "form.more": "추가 옵션",
+ "form.backend": "백엔드",
+ "form.token": "Hugging Face 토큰",
+ "fineTune.starting": "시작 중...",
+ "fineTune.noMessage": "백엔드가 메시지를 보내지 않았습니다.",
+ "fineTune.tryTitle": "다음에 할 일",
+ "fineTune.reuse": "설정 다시 열기",
+ "stop.cancelDelete": "취소",
+ "log.title": "로그",
+ "log.note": "이 페이지가 열린 뒤 작업이 보고한 내용입니다.",
+ "log.only": "경고와 오류만",
+ "log.copy": "복사",
+ "log.copied": "복사됨",
+ "log.download": "다운로드",
+ "log.empty": "아직 보고된 내용이 없습니다.",
+ "log.noneFiltered": "경고나 오류가 없습니다.",
+ "jobs.title": "이전 작업",
+ "jobs.count_one": "이 서버에 {{count}}개 보관됨",
+ "jobs.count_other": "이 서버에 {{count}}개 보관됨",
+ "jobs.loading": "작업 불러오는 중",
+ "jobs.emptyTitle": "아직 파인튜닝 작업이 없습니다",
+ "jobs.emptyText": "위에서 작업을 설정하세요. 작업은 상태와 함께 여기에 나타나며, 나중에 설정을 재사용할 수 있습니다.",
+ "jobs.model": "모델",
+ "jobs.kind": "종류",
+ "jobs.status": "상태",
+ "jobs.started": "시작 시간",
+ "jobs.actions": "작업",
+ "jobs.open": "이 작업 열기",
+ "jobs.reuse": "재사용",
+ "jobs.reuseTitle": "이 작업의 설정을 새 작업에 사용",
+ "jobs.delete": "삭제",
+ "jobs.deleteTitle": "이 작업과 데이터 삭제",
+ "jobs.deleteDialogTitle": "이 작업을 삭제할까요?",
+ "jobs.deleteDialogText": "작업을 체크포인트와 내보낸 모델과 함께 삭제합니다. 이 작업은 되돌릴 수 없습니다.",
+ "quantize.title": "양자화",
+ "quantize.lede": "모델을 더 작게 만들어 더 적은 메모리에 맞춥니다. 결과는 가져올 수 있는 GGUF 파일입니다.",
+ "quantize.newJob": "새 작업",
+ "quantize.sectionModel": "모델",
+ "quantize.model": "모델",
+ "quantize.modelHint": "Hugging Face id 또는 이 호스트의 경로.",
+ "quantize.sectionPrecision": "정밀도",
+ "quantize.type": "양자화 유형",
+ "quantize.typeHint": "숫자가 작을수록 파일이 작아지고 품질이 조금 떨어집니다.",
+ "quantize.custom": "사용자 지정...",
+ "quantize.customLabel": "사용자 지정 유형",
+ "quantize.customPlaceholder": "예: iq4_nl",
+ "quantize.moreHint": "백엔드, Hugging Face 토큰",
+ "quantize.tokenHint": "접근이 제한된 모델에만 필요합니다.",
+ "quantize.recipe": "{{model}}을(를) {{type}} GGUF로 변환합니다.",
+ "quantize.recipeEmpty": "모델을 입력하면 만들어질 결과를 볼 수 있습니다.",
+ "quantize.start": "양자화 ({{type}})",
+ "quantize.barNote": "시작하기 전까지는 아무것도 바뀌지 않습니다.",
+ "quantize.jobMeta": "{{backend}}의 {{type}}, 시작 {{started}}",
+ "quantize.failed": "양자화 실패",
+ "quantize.finished": "양자화 완료: {{type}}.",
+ "quantize.stopped": "작업이 중지되었습니다.",
+ "quantize.tryText": "설정을 다시 열고, 메시지가 가리키는 항목을 바꾼 뒤 새 작업을 시작하세요. 새 백엔드는 오래된 백엔드가 지원하지 않는 아키텍처를 지원할 수 있습니다.",
+ "quantize.outputTitle": "결과",
+ "quantize.outputText": "{{type}}의 GGUF 파일입니다. LocalAI에서 쓰려면 가져오거나 다운로드하세요.",
+ "quantize.file": "파일",
+ "quantize.importName": "모델 이름",
+ "quantize.importNamePlaceholder": "비워 두면 자동 생성",
+ "quantize.importBlocked": "이 서버는 애드온 호스트와 모델 폴더를 공유하지 않으므로 가져오기가 꺼져 있습니다.",
+ "quantize.import": "LocalAI로 가져오기",
+ "quantize.importing": "가져오는 중...",
+ "quantize.importFailed": "가져오기 실패",
+ "quantize.importFailedWith": "가져오기 실패: {{message}}",
+ "quantize.download": "GGUF 다운로드",
+ "quantize.nextTitle": "{{name}}이(가) 이제 모델입니다",
+ "quantize.nextText": "LocalAI에 등록되었습니다. 이 모델과 채팅을 열거나 모델 페이지에서 찾으세요.",
+ "quantize.chat": "{{name}}과(와) 채팅",
+ "quantize.models": "모델에서 열기",
+ "quantize.imported": "{{name}}(으)로 가져왔습니다",
+ "quantize.emptyTitle": "아직 양자화 작업이 없습니다",
+ "quantize.emptyText": "위에서 작업을 설정하세요. 작업은 상태와 함께 여기에 나타납니다.",
+ "quantize.deleteDialogText": "작업을 변환된 파일과 함께 삭제합니다. 이 작업은 되돌릴 수 없습니다.",
+ "quantize.startFailed": "작업을 시작하지 못했습니다",
+ "quantize.stopFailed": "작업을 중지하지 못했습니다",
+ "quantize.deleteFailed": "작업을 삭제하지 못했습니다",
+ "quantize.status.queued": "대기 중",
+ "quantize.status.downloading": "다운로드 중",
+ "quantize.status.converting": "변환 중",
+ "quantize.status.quantizing": "양자화 중",
+ "quantize.status.completed": "완료됨",
+ "quantize.status.failed": "실패",
+ "quantize.status.stopped": "중지됨",
+ "off.badge": "사용할 수 없음",
+ "off.fineTune.title": "이 계정은 파인튜닝을 할 수 없습니다",
+ "off.fineTune.text": "파인튜닝은 별도의 권한이며, 이 계정에는 그 권한이 없습니다.",
+ "off.quantize.title": "이 계정은 양자화를 할 수 없습니다",
+ "off.quantize.text": "양자화는 별도의 권한이며, 이 계정에는 그 권한이 없습니다.",
+ "off.admin": "관리자가 사용자 및 키 메뉴에서 켤 수 있습니다.",
+ "off.askAdmin": "관리자에게 이 계정에 대해 켜 달라고 요청하세요.",
+ "off.back": "홈으로 이동",
+ "run.unknownPercent": "진행률이 아직 보고되지 않았습니다"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.pt-BR.json b/addons/quantization/ui/src/i18n/quantization.pt-BR.json
new file mode 100644
index 0000000000..ee9754958d
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.pt-BR.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "Progresso",
+ "steps.setup": "Configurar",
+ "steps.check": "Verificar",
+ "steps.run": "Executar",
+ "steps.result": "Resultado",
+ "run.stop": "Parar",
+ "run.connecting": "Conectando ao job. Aguardando o primeiro evento de progresso.",
+ "run.progress": "Progresso",
+ "run.stages": "Etapas",
+ "run.step": "passo",
+ "run.of": "de",
+ "run.epoch": "época",
+ "run.tokens": "tokens/s",
+ "run.eta": "cerca de {{time}} restantes",
+ "run.etaSeconds": "cerca de {{seconds}} s restantes",
+ "landing.experimental": "Experimental",
+ "checks.heading": "Verifique antes de começar",
+ "checks.live": "Atualiza conforme você muda a configuração.",
+ "checks.meterLabel": "{{used}} por cento em uso, {{free}} livres de {{total}}",
+ "checks.openBackends": "Abrir Backends",
+ "checks.barOk": "Nada impede o início.",
+ "checks.barWarn_one": "{{count}} aviso. Você ainda pode iniciar.",
+ "checks.barWarn_other": "{{count}} avisos. Você ainda pode iniciar.",
+ "checks.title.model": "Modelo",
+ "checks.title.data": "Dataset",
+ "checks.title.backend": "Backend",
+ "checks.title.memory": "Memória",
+ "checks.title.disk": "Disco",
+ "checks.title.reward": "Funções de recompensa",
+ "checks.title.resume": "Retomando",
+ "checks.title.access": "Acesso ao modelo",
+ "checks.title.type": "Tipo de quantização",
+ "checks.title.source": "Origem",
+ "checks.title.name": "Nome",
+ "checks.fineTune.modelOk": "Treinando {{model}}.",
+ "checks.fineTune.modelMissing": "Informe o modelo a treinar.",
+ "checks.fineTune.dataOk": "Os dados vêm de {{source}}.",
+ "checks.fineTune.dataMissing": "Adicione um dataset: um id do Hugging Face ou um arquivo enviado.",
+ "checks.fineTune.backendOk": "{{name}} está instalado.",
+ "checks.fineTune.backendMissing": "Nenhum backend de ajuste fino está instalado, então o job pode não iniciar.",
+ "checks.fineTune.backendOther": "{{name}} não está entre os backends de ajuste fino instalados ({{list}}).",
+ "checks.fineTune.backendUnknown": "Não foi possível ler os backends instalados.",
+ "checks.fineTune.memoryGpu": "{{name}}: {{free}} de {{total}} livres. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}, acumulação de gradiente {{accum}}. Quanto o job precisa depende do modelo e destas configurações, e o LocalAI não estima isso antes de o job começar.",
+ "checks.fineTune.memoryCluster": "{{total}} no melhor nó ({{node}}). Tamanho do lote {{batch}}, comprimento da sequência {{seq}}. Quanto o job precisa depende do modelo e destas configurações.",
+ "checks.fineTune.memoryCpu": "Nenhuma GPU encontrada. O treino usa a CPU e {{free}} de {{total}} de RAM estão livres. Espere que seja lento. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}.",
+ "checks.fineTune.memoryUnknown": "Esta conta não consegue ler a memória da máquina. Tamanho do lote {{batch}}, comprimento da sequência {{seq}}. Quanto o job precisa depende do modelo e destas configurações.",
+ "checks.fineTune.disk": "{{free}} livres no disco de modelos. Downloads e checkpoints precisam de espaço.",
+ "checks.fineTune.diskLow": "Só {{free}} livres no disco de modelos. Downloads e checkpoints precisam de espaço.",
+ "checks.fineTune.rewardMissing": "GRPO precisa de pelo menos uma função de recompensa. Adicione uma em Mais opções.",
+ "checks.fineTune.resume": "O treino continua a partir de {{path}}.",
+ "checks.fineTune.accessNone": "Nenhum token definido. Um modelo com acesso restrito precisa de um token do Hugging Face em Mais opções.",
+ "checks.fineTune.accessSet": "Um token do Hugging Face está definido.",
+ "checks.quantize.modelOk": "Quantizando {{model}}.",
+ "checks.quantize.modelMissing": "Informe o modelo a quantizar.",
+ "checks.quantize.typeMissing": "Informe o tipo de quantização personalizado.",
+ "checks.quantize.backendOk": "{{name}} está instalado.",
+ "checks.quantize.backendMissing": "Nenhum backend de quantização está instalado, então o job pode não iniciar.",
+ "checks.quantize.backendOther": "{{name}} não está entre os backends de quantização instalados ({{list}}).",
+ "checks.quantize.backendUnknown": "Não foi possível ler os backends instalados.",
+ "checks.quantize.memory": "{{free}} de {{total}} de RAM livres. A conversão roda na CPU.",
+ "checks.quantize.disk": "{{free}} livres no disco de modelos. O download, o arquivo convertido e o resultado são gravados em disco.",
+ "checks.quantize.diskLow": "Só {{free}} livres no disco de modelos. O download, o arquivo convertido e o resultado são gravados em disco.",
+ "checks.quantize.accessNone": "Nenhum token definido. Um modelo com acesso restrito precisa de um token do Hugging Face em Mais opções.",
+ "checks.quantize.accessSet": "Um token do Hugging Face está definido.",
+ "checks.import.sourceOk_huggingface": "Reconhecido como um repositório do Hugging Face. O LocalAI verifica os arquivos dele ao importar.",
+ "checks.import.sourceOk_url": "Formato de URL reconhecido. O conteúdo não foi verificado.",
+ "checks.import.sourceOk_config": "Reconhecido como um arquivo de configuração de modelo. Ele é lido e usado como está.",
+ "checks.import.sourceOk_oci": "Reconhecido como uma referência de imagem OCI.",
+ "checks.import.sourceOk_ollama": "Reconhecido como uma referência de modelo do Ollama.",
+ "checks.import.sourceOk_local": "Reconhecido como um caminho na máquina que executa o LocalAI.",
+ "checks.import.sourceOk": "A origem foi reconhecida.",
+ "checks.import.sourceUnknown": "Isto não parece com nenhum formato que o LocalAI conheça. Ele vai tentar mesmo assim, e pode falhar.",
+ "checks.import.backendDownload": "{{name}} ainda não está instalado. O LocalAI o baixa primeiro.",
+ "checks.import.backendChosen": "{{name}} foi escolhido e está instalado.",
+ "checks.import.backendAuto": "O LocalAI escolhe o backend pelos arquivos da origem quando a importação começa.",
+ "checks.import.nameTaken": "Já existe um modelo instalado chamado {{name}}.",
+ "checks.import.nameFree": "{{name}} não está em uso.",
+ "checks.import.nameAuto": "O modelo recebe o nome da origem.",
+ "checks.import.disk": "{{free}} livres no disco de modelos. O tamanho do download é conhecido quando a importação começa.",
+ "checks.import.memory_gpu": "{{free}} de memória da GPU estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory_ram": "{{free}} de RAM estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory_cluster": "{{free}} estão disponíveis no melhor nó. O que este modelo precisa é conhecido quando a importação começa.",
+ "checks.import.memory": "{{free}} de memória estão livres agora. O que este modelo precisa é conhecido quando a importação começa.",
+ "form.more": "Mais opções",
+ "form.backend": "Backend",
+ "form.token": "Token do Hugging Face",
+ "fineTune.starting": "Iniciando...",
+ "fineTune.noMessage": "O backend não enviou mensagem.",
+ "fineTune.tryTitle": "O que fazer a seguir",
+ "fineTune.reuse": "Abrir a configuração de novo",
+ "stop.cancelDelete": "Cancelar",
+ "log.title": "Log",
+ "log.note": "O que o job informou desde que esta página foi aberta.",
+ "log.only": "Somente avisos e erros",
+ "log.copy": "Copiar",
+ "log.copied": "Copiado",
+ "log.download": "Baixar",
+ "log.empty": "Nada informado ainda.",
+ "log.noneFiltered": "Nenhum aviso ou erro.",
+ "jobs.title": "Jobs anteriores",
+ "jobs.count_one": "{{count}} mantido neste servidor",
+ "jobs.count_other": "{{count}} mantidos neste servidor",
+ "jobs.loading": "Carregando jobs",
+ "jobs.emptyTitle": "Nenhum job de ajuste fino ainda",
+ "jobs.emptyText": "Configure um acima. Ele aparece aqui com o seu status, e você pode reutilizar a configuração depois.",
+ "jobs.model": "Modelo",
+ "jobs.kind": "Tipo",
+ "jobs.status": "Status",
+ "jobs.started": "Iniciado",
+ "jobs.actions": "Ações",
+ "jobs.open": "Abrir este job",
+ "jobs.reuse": "Reutilizar",
+ "jobs.reuseTitle": "Usar a configuração deste job em um novo job",
+ "jobs.delete": "Excluir",
+ "jobs.deleteTitle": "Excluir este job e os seus dados",
+ "jobs.deleteDialogTitle": "Excluir este job?",
+ "jobs.deleteDialogText": "Isto exclui o job com os seus checkpoints e qualquer modelo exportado. Não pode ser desfeito.",
+ "quantize.title": "Quantizar",
+ "quantize.lede": "Reduza um modelo para que ele caiba em menos memória. O resultado é um arquivo GGUF que você pode importar.",
+ "quantize.newJob": "Novo job",
+ "quantize.sectionModel": "Modelo",
+ "quantize.model": "Modelo",
+ "quantize.modelHint": "Um id do Hugging Face ou um caminho nesta máquina.",
+ "quantize.sectionPrecision": "Precisão",
+ "quantize.type": "Tipo de quantização",
+ "quantize.typeHint": "Um número menor significa arquivo menor e um pouco menos de qualidade.",
+ "quantize.custom": "Personalizado...",
+ "quantize.customLabel": "Tipo personalizado",
+ "quantize.customPlaceholder": "por exemplo iq4_nl",
+ "quantize.moreHint": "backend, token do Hugging Face",
+ "quantize.tokenHint": "Só é necessário para modelos com acesso restrito.",
+ "quantize.recipe": "Converter {{model}} para GGUF em {{type}}.",
+ "quantize.recipeEmpty": "Informe um modelo para ver o que será criado.",
+ "quantize.start": "Quantizar ({{type}})",
+ "quantize.barNote": "Nada é alterado até você iniciar.",
+ "quantize.jobMeta": "{{type}} em {{backend}}, iniciado {{started}}",
+ "quantize.failed": "A quantização falhou",
+ "quantize.finished": "Quantização concluída: {{type}}.",
+ "quantize.stopped": "O job foi interrompido.",
+ "quantize.tryText": "Abra a configuração de novo, altere o que a mensagem aponta e inicie um novo job. Um backend mais novo pode suportar uma arquitetura que um mais antigo não suporta.",
+ "quantize.outputTitle": "O resultado",
+ "quantize.outputText": "Um arquivo GGUF em {{type}}. Importe-o para usar no LocalAI, ou baixe-o.",
+ "quantize.file": "Arquivo",
+ "quantize.importName": "Nome do modelo",
+ "quantize.importNamePlaceholder": "Gerado se vazio",
+ "quantize.importBlocked": "A importação está desativada neste servidor porque ele não compartilha a pasta de modelos com o host do complemento.",
+ "quantize.import": "Importar para o LocalAI",
+ "quantize.importing": "Importando...",
+ "quantize.importFailed": "A importação falhou",
+ "quantize.importFailedWith": "A importação falhou: {{message}}",
+ "quantize.download": "Baixar GGUF",
+ "quantize.nextTitle": "{{name}} agora é um modelo",
+ "quantize.nextText": "Ele está registrado no LocalAI. Abra um chat com ele ou encontre-o em Modelos.",
+ "quantize.chat": "Conversar com {{name}}",
+ "quantize.models": "Abrir em Modelos",
+ "quantize.imported": "importado como {{name}}",
+ "quantize.emptyTitle": "Nenhum job de quantização ainda",
+ "quantize.emptyText": "Configure um acima. Ele aparece aqui com o seu status.",
+ "quantize.deleteDialogText": "Isto exclui o job com os seus arquivos convertidos. Não pode ser desfeito.",
+ "quantize.startFailed": "Não foi possível iniciar o job",
+ "quantize.stopFailed": "Não foi possível parar o job",
+ "quantize.deleteFailed": "Não foi possível excluir o job",
+ "quantize.status.queued": "Na fila",
+ "quantize.status.downloading": "Baixando",
+ "quantize.status.converting": "Convertendo",
+ "quantize.status.quantizing": "Quantizando",
+ "quantize.status.completed": "Concluído",
+ "quantize.status.failed": "Falhou",
+ "quantize.status.stopped": "Parado",
+ "off.badge": "Não disponível para você",
+ "off.fineTune.title": "Sua conta não pode fazer ajuste fino",
+ "off.fineTune.text": "O ajuste fino é uma permissão separada, e esta conta não a tem.",
+ "off.quantize.title": "Sua conta não pode quantizar",
+ "off.quantize.text": "A quantização é uma permissão separada, e esta conta não a tem.",
+ "off.admin": "Um administrador pode ativá-la em Usuários e chaves.",
+ "off.askAdmin": "Peça a um administrador para ativá-la na sua conta.",
+ "off.back": "Ir para o Início",
+ "run.unknownPercent": "Progresso ainda não informado"
+}
diff --git a/addons/quantization/ui/src/i18n/quantization.zh-CN.json b/addons/quantization/ui/src/i18n/quantization.zh-CN.json
new file mode 100644
index 0000000000..1b2fe3d3a4
--- /dev/null
+++ b/addons/quantization/ui/src/i18n/quantization.zh-CN.json
@@ -0,0 +1,180 @@
+{
+ "steps.label": "进度",
+ "steps.setup": "设置",
+ "steps.check": "检查",
+ "steps.run": "运行",
+ "steps.result": "结果",
+ "run.stop": "停止",
+ "run.connecting": "正在连接任务。等待第一个进度事件。",
+ "run.progress": "进度",
+ "run.stages": "阶段",
+ "run.step": "步",
+ "run.of": "/",
+ "run.epoch": "轮次",
+ "run.tokens": "tokens/s",
+ "run.eta": "约剩 {{time}}",
+ "run.etaSeconds": "约剩 {{seconds}} 秒",
+ "landing.experimental": "实验性",
+ "checks.heading": "开始前检查",
+ "checks.live": "随您修改设置而更新。",
+ "checks.meterLabel": "已用 {{used}}%,{{total}} 中 {{free}} 可用",
+ "checks.openBackends": "打开后端",
+ "checks.barOk": "没有任何项目阻止启动。",
+ "checks.barWarn_one": "{{count}} 条警告。仍可以启动。",
+ "checks.barWarn_other": "{{count}} 条警告。仍可以启动。",
+ "checks.title.model": "模型",
+ "checks.title.data": "数据集",
+ "checks.title.backend": "后端",
+ "checks.title.memory": "内存",
+ "checks.title.disk": "磁盘",
+ "checks.title.reward": "奖励函数",
+ "checks.title.resume": "恢复",
+ "checks.title.access": "模型访问",
+ "checks.title.type": "量化类型",
+ "checks.title.source": "来源",
+ "checks.title.name": "名称",
+ "checks.fineTune.modelOk": "正在训练 {{model}}。",
+ "checks.fineTune.modelMissing": "请输入要训练的模型。",
+ "checks.fineTune.dataOk": "数据来自 {{source}}。",
+ "checks.fineTune.dataMissing": "请添加数据集:Hugging Face id 或上传的文件。",
+ "checks.fineTune.backendOk": "{{name}} 已安装。",
+ "checks.fineTune.backendMissing": "未安装微调后端,任务可能无法启动。",
+ "checks.fineTune.backendOther": "{{name}} 不在已安装的微调后端({{list}})中。",
+ "checks.fineTune.backendUnknown": "无法读取已安装的后端。",
+ "checks.fineTune.memoryGpu": "{{name}}:{{total}} 中 {{free}} 可用。批次大小 {{batch}},序列长度 {{seq}},梯度累积 {{accum}}。任务所需的内存取决于模型和这些设置,LocalAI 在任务开始前不做估算。",
+ "checks.fineTune.memoryCluster": "最佳节点({{node}})上的 {{total}}。批次大小 {{batch}},序列长度 {{seq}}。任务所需的内存取决于模型和这些设置。",
+ "checks.fineTune.memoryCpu": "未找到 GPU。训练使用 CPU,RAM {{total}} 中有 {{free}} 可用。预计会很慢。批次大小 {{batch}},序列长度 {{seq}}。",
+ "checks.fineTune.memoryUnknown": "此账户无法读取机器的内存。批次大小 {{batch}},序列长度 {{seq}}。任务所需的内存取决于模型和这些设置。",
+ "checks.fineTune.disk": "模型磁盘上可用 {{free}}。下载和检查点需要空间。",
+ "checks.fineTune.diskLow": "模型磁盘上仅剩 {{free}} 可用。下载和检查点需要空间。",
+ "checks.fineTune.rewardMissing": "GRPO 至少需要一个奖励函数。请在“更多选项”中添加。",
+ "checks.fineTune.resume": "训练将从 {{path}} 继续。",
+ "checks.fineTune.accessNone": "未设置令牌。受限模型需要在“更多选项”中设置 Hugging Face 令牌。",
+ "checks.fineTune.accessSet": "已设置 Hugging Face 令牌。",
+ "checks.quantize.modelOk": "正在量化 {{model}}。",
+ "checks.quantize.modelMissing": "请输入要量化的模型。",
+ "checks.quantize.typeMissing": "请输入自定义量化类型。",
+ "checks.quantize.backendOk": "{{name}} 已安装。",
+ "checks.quantize.backendMissing": "未安装量化后端,任务可能无法启动。",
+ "checks.quantize.backendOther": "{{name}} 不在已安装的量化后端({{list}})中。",
+ "checks.quantize.backendUnknown": "无法读取已安装的后端。",
+ "checks.quantize.memory": "RAM {{total}} 中有 {{free}} 可用。转换在 CPU 上运行。",
+ "checks.quantize.disk": "模型磁盘上可用 {{free}}。下载、转换后的文件和结果都会写入磁盘。",
+ "checks.quantize.diskLow": "模型磁盘上仅剩 {{free}} 可用。下载、转换后的文件和结果都会写入磁盘。",
+ "checks.quantize.accessNone": "未设置令牌。受限模型需要在“更多选项”中设置 Hugging Face 令牌。",
+ "checks.quantize.accessSet": "已设置 Hugging Face 令牌。",
+ "checks.import.sourceOk_huggingface": "识别为 Hugging Face 仓库。导入时 LocalAI 会检查其中的文件。",
+ "checks.import.sourceOk_url": "已识别 URL 格式。其内容尚未检查。",
+ "checks.import.sourceOk_config": "识别为模型配置文件。将按原样读取并使用。",
+ "checks.import.sourceOk_oci": "识别为 OCI 镜像引用。",
+ "checks.import.sourceOk_ollama": "识别为 Ollama 模型引用。",
+ "checks.import.sourceOk_local": "识别为运行 LocalAI 的机器上的路径。",
+ "checks.import.sourceOk": "已识别来源。",
+ "checks.import.sourceUnknown": "这看起来不是 LocalAI 支持的任何格式。仍会尝试,但可能失败。",
+ "checks.import.backendDownload": "{{name}} 尚未安装。LocalAI 会先下载它。",
+ "checks.import.backendChosen": "已选择并安装 {{name}}。",
+ "checks.import.backendAuto": "导入开始时,LocalAI 会根据来源的文件选择后端。",
+ "checks.import.nameTaken": "名为 {{name}} 的模型已安装。",
+ "checks.import.nameFree": "{{name}} 未被使用。",
+ "checks.import.nameAuto": "模型将以来源命名。",
+ "checks.import.disk": "模型磁盘上可用 {{free}}。导入开始后即可知道下载大小。",
+ "checks.import.memory_gpu": "当前 GPU 显存可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory_ram": "当前 RAM 可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory_cluster": "最佳节点上可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "checks.import.memory": "当前内存可用 {{free}}。此模型需要多少,导入开始后即可知道。",
+ "form.more": "更多选项",
+ "form.backend": "后端",
+ "form.token": "Hugging Face 令牌",
+ "fineTune.starting": "正在启动...",
+ "fineTune.noMessage": "后端没有提供消息。",
+ "fineTune.tryTitle": "下一步该怎么做",
+ "fineTune.reuse": "重新打开设置",
+ "stop.cancelDelete": "取消",
+ "log.title": "日志",
+ "log.note": "自打开此页面以来任务报告的内容。",
+ "log.only": "仅显示警告和错误",
+ "log.copy": "复制",
+ "log.copied": "已复制",
+ "log.download": "下载",
+ "log.empty": "尚无报告。",
+ "log.noneFiltered": "没有警告或错误。",
+ "jobs.title": "早前的任务",
+ "jobs.count_one": "此服务器保留 {{count}} 个",
+ "jobs.count_other": "此服务器保留 {{count}} 个",
+ "jobs.loading": "正在加载任务",
+ "jobs.emptyTitle": "还没有微调任务",
+ "jobs.emptyText": "在上方进行设置。任务会连同状态显示在这里,之后您可以复用其设置。",
+ "jobs.model": "模型",
+ "jobs.kind": "类型",
+ "jobs.status": "状态",
+ "jobs.started": "开始时间",
+ "jobs.actions": "操作",
+ "jobs.open": "打开此任务",
+ "jobs.reuse": "复用",
+ "jobs.reuseTitle": "将此任务的设置用于新任务",
+ "jobs.delete": "删除",
+ "jobs.deleteTitle": "删除此任务及其数据",
+ "jobs.deleteDialogTitle": "删除此任务?",
+ "jobs.deleteDialogText": "这将删除任务及其检查点和任何已导出的模型。此操作无法撤销。",
+ "quantize.title": "量化",
+ "quantize.lede": "缩小模型,使其占用更少内存。结果是可以导入的 GGUF 文件。",
+ "quantize.newJob": "新建任务",
+ "quantize.sectionModel": "模型",
+ "quantize.model": "模型",
+ "quantize.modelHint": "Hugging Face id,或本机上的路径。",
+ "quantize.sectionPrecision": "精度",
+ "quantize.type": "量化类型",
+ "quantize.typeHint": "数字越小,文件越小,质量会略有下降。",
+ "quantize.custom": "自定义...",
+ "quantize.customLabel": "自定义类型",
+ "quantize.customPlaceholder": "例如 iq4_nl",
+ "quantize.moreHint": "后端、Hugging Face 令牌",
+ "quantize.tokenHint": "仅受限模型需要。",
+ "quantize.recipe": "将 {{model}} 转换为 {{type}} 格式的 GGUF。",
+ "quantize.recipeEmpty": "输入模型,即可查看将要生成的内容。",
+ "quantize.start": "量化({{type}})",
+ "quantize.barNote": "在您开始之前,不会更改任何内容。",
+ "quantize.jobMeta": "{{backend}} 上的 {{type}},开始于 {{started}}",
+ "quantize.failed": "量化失败",
+ "quantize.finished": "量化完成:{{type}}。",
+ "quantize.stopped": "任务已停止。",
+ "quantize.tryText": "重新打开设置,修改消息所指的内容,然后启动新任务。较新的后端可能支持较旧后端不支持的架构。",
+ "quantize.outputTitle": "结果",
+ "quantize.outputText": "{{type}} 格式的 GGUF 文件。导入后即可在 LocalAI 中使用,也可以直接下载。",
+ "quantize.file": "文件",
+ "quantize.importName": "模型名称",
+ "quantize.importNamePlaceholder": "留空则自动生成",
+ "quantize.importBlocked": "此服务器未与附加组件主机共享模型文件夹,因此已关闭导入。",
+ "quantize.import": "导入到 LocalAI",
+ "quantize.importing": "正在导入...",
+ "quantize.importFailed": "导入失败",
+ "quantize.importFailedWith": "导入失败:{{message}}",
+ "quantize.download": "下载 GGUF",
+ "quantize.nextTitle": "{{name}} 现已成为模型",
+ "quantize.nextText": "它已在 LocalAI 中注册。打开与它的对话,或在模型页面中找到它。",
+ "quantize.chat": "与 {{name}} 对话",
+ "quantize.models": "在模型中打开",
+ "quantize.imported": "已导入为 {{name}}",
+ "quantize.emptyTitle": "还没有量化任务",
+ "quantize.emptyText": "在上方进行设置。任务会连同状态显示在这里。",
+ "quantize.deleteDialogText": "这将删除任务及其转换后的文件。此操作无法撤销。",
+ "quantize.startFailed": "启动任务失败",
+ "quantize.stopFailed": "停止任务失败",
+ "quantize.deleteFailed": "删除任务失败",
+ "quantize.status.queued": "排队中",
+ "quantize.status.downloading": "下载中",
+ "quantize.status.converting": "转换中",
+ "quantize.status.quantizing": "量化中",
+ "quantize.status.completed": "已完成",
+ "quantize.status.failed": "失败",
+ "quantize.status.stopped": "已停止",
+ "off.badge": "您无法使用",
+ "off.fineTune.title": "您的账户无法进行微调",
+ "off.fineTune.text": "微调是一项单独的权限,此账户没有该权限。",
+ "off.quantize.title": "您的账户无法进行量化",
+ "off.quantize.text": "量化是一项单独的权限,此账户没有该权限。",
+ "off.admin": "管理员可以在“用户和密钥”中开启该权限。",
+ "off.askAdmin": "请管理员为您的账户开启该权限。",
+ "off.back": "前往首页",
+ "run.unknownPercent": "尚未报告进度"
+}
diff --git a/addons/quantization/ui/test/page.test.mjs b/addons/quantization/ui/test/page.test.mjs
index 3f4031efa9..02559062a5 100644
--- a/addons/quantization/ui/test/page.test.mjs
+++ b/addons/quantization/ui/test/page.test.mjs
@@ -5,9 +5,9 @@ import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { load, draw, here, hostUi, React, renderToString } from './load.mjs'
import { createApi } from '../src/api.js'
-import en from '../src/i18n/en.js'
const read = (p) => readFileSync(p, 'utf8')
+const en = JSON.parse(read(join(here, '../src/i18n/quantization.en.json')))
const src = join(here, '../src')
const toolsUi = join(here, '../../../internal/tools-ui/src')
const files = (dir) => readdirSync(dir, { recursive: true }).filter(f => /\.(jsx?|mjs)$/.test(f)).map(f => join(dir, f))
@@ -180,7 +180,7 @@ test('icons: every icon name the page draws is one the SDK knows', async () => {
for (const name of used) assert.ok(known.has(name), `unknown icon ${name}`)
})
-test('i18n: every literal key the add-on and the shared views read is in en.js', () => {
+test('i18n: every literal key the add-on and the shared views read is in the English bundle', () => {
const keys = new Set()
for (const text of [ownText, sharedText]) for (const m of text.matchAll(/\bt\(\s*'([A-Za-z0-9_.]+)'/g)) keys.add(m[1])
assert.ok(keys.size > 60, `found only ${keys.size} literal keys`)
@@ -188,7 +188,7 @@ test('i18n: every literal key the add-on and the shared views read is in en.js',
assert.deepEqual(missing, [])
})
-test('i18n: en.js repeats the host strings and adds only the keys the add-on owns', () => {
+test('i18n: the English bundle repeats the host strings and adds only the keys the add-on owns', () => {
const tools = Object.fromEntries(flatten(JSON.parse(read(join(hostUi, 'public/locales/en/tools.json')))))
const own = []
for (const [k, v] of Object.entries(en)) {
diff --git a/addons/voice/ui/src/ctx.jsx b/addons/voice/ui/src/ctx.jsx
index 324b6107bd..e8696b295d 100644
--- a/addons/voice/ui/src/ctx.jsx
+++ b/addons/voice/ui/src/ctx.jsx
@@ -1,4 +1,4 @@
-import en from './i18n/en.js'
+import { locales } from './i18n/index.js'
import { modelFrom as pickModel } from './lib/model.js'
// The one object every page factory of the Voice add-on receives. It binds the
@@ -8,12 +8,25 @@ export function createContext(sdk) {
const { React } = sdk
const h = React.createElement
const Fragment = React.Fragment
- sdk.locale.define('en', Object.fromEntries(Object.entries(en).filter(([, v]) => typeof v === 'string')))
- const lists = Object.fromEntries(Object.entries(en).filter(([, v]) => Array.isArray(v)))
+ // Every language of the page; a key a language lacks reads in English.
+ for (const [language, bundle] of Object.entries(locales)) sdk.locale.define(language, bundle)
+ const en = locales.en
const t = (key, vars) => sdk.locale.t(key, key, vars)
- // A list of strings or objects (the "why" and "stored" notes).
- const list = (key) => lists[key] || []
+ // A list of strings or objects (the "why" and "stored" notes). The bundles hold
+ // each item under a numbered key (key.0, or key.0.field for an object), so
+ // that every item is translated like any other text.
+ const list = (key) => {
+ const out = []
+ for (let i = 0; ; i++) {
+ const own = `${key}.${i}`
+ if (typeof en[own] === 'string') { out.push(t(own)); continue }
+ const prefix = `${own}.`
+ const fields = Object.keys(en).filter((k) => k.startsWith(prefix))
+ if (fields.length === 0) return out
+ out.push(Object.fromEntries(fields.map((k) => [k.slice(prefix.length), t(k)])))
+ }
+ }
// A link to a page of this add-on, as an anchor the shell navigates.
function Link({ page, query, className, children, ...rest }) {
diff --git a/addons/voice/ui/src/i18n/index.js b/addons/voice/ui/src/i18n/index.js
new file mode 100644
index 0000000000..2a0ea9c09d
--- /dev/null
+++ b/addons/voice/ui/src/i18n/index.js
@@ -0,0 +1,16 @@
+// The words of the voice pages, one JSON bundle per language of the web UI.
+// English is the source and the fallback of every key. Italian is a draft
+// that wants a native review; a language whose bundle repeats the English
+// text of a key has no translation for it yet. addons/internal/i18ncheck
+// checks that every bundle has the keys and placeholders of English.
+import en from './voice.en.json'
+import it from './voice.it.json'
+import de from './voice.de.json'
+import es from './voice.es.json'
+import fr from './voice.fr.json'
+import id from './voice.id.json'
+import ko from './voice.ko.json'
+import pt_BR from './voice.pt-BR.json'
+import zh_CN from './voice.zh-CN.json'
+
+export const locales = { en, it, de, es, fr, id, ko, 'pt-BR': pt_BR, 'zh-CN': zh_CN }
diff --git a/addons/voice/ui/src/i18n/voice.de.json b/addons/voice/ui/src/i18n/voice.de.json
new file mode 100644
index 0000000000..432c0d2b6c
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.de.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voice",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "Text",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "Ergebnis",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "Optionale Stimm-ID",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "Stimmen",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "Erkennung",
+ "voices.tab.library": "Bibliothek",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "In Arbeit",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Name",
+ "known.added": "Hinzugefügt",
+ "known.actions": "Aktionen",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Rückgängig",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "{{name}} entfernen",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "Schließen",
+ "enrol.cancel": "Abbrechen",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Name",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "Text zu Sprache",
+ "tts.labels.model": "Modell",
+ "tts.labels.voice": "Stimme",
+ "tts.labels.voicePlaceholder": "Optionale Stimm-ID",
+ "tts.labels.input": "Text",
+ "tts.labels.inputPlaceholder": "Text zum Synthetisieren eingeben...",
+ "tts.labels.instructions": "Anweisungen",
+ "tts.labels.instructionsPlaceholder": "Zum Beispiel: Langsam und warmherzig sprechen",
+ "tts.labels.instructionsHint": "Optionale Hinweise zur Sprechweise. Die Unterstützung hängt vom Backend ab.",
+ "tts.actions.generate": "Audio generieren",
+ "tts.actions.generating": "Generieren...",
+ "tts.empty": "Generiertes Audio erscheint hier",
+ "tts.toasts.noText": "Bitte Text eingeben",
+ "tts.toasts.noModel": "Bitte ein Modell auswählen",
+ "tts.toasts.generated": "Speech generated",
+ "tts.toasts.generateFailed": "Generierung fehlgeschlagen",
+ "tts.voiceLibrary.cloningReady": "Voice cloning",
+ "tts.voiceLibrary.loading": "Loading saved personalities…",
+ "tts.voiceLibrary.modelDefault": "Use model default",
+ "tts.voiceLibrary.empty": "No saved personalities yet.",
+ "tts.voiceLibrary.create": "Create one",
+ "tts.voiceLibrary.manage": "Manage Personality Library",
+ "tts.voiceLibrary.namedVoiceHint": "This model does not accept reference-audio profiles. You can still enter a named speaker supported by its backend.",
+ "voiceLibrary.title": "Speech voices",
+ "voiceLibrary.subtitle": "A recording and its exact transcript. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Loading saved personalities…",
+ "voiceLibrary.listLabel": "Saved voice personalities",
+ "voiceLibrary.status.ready": "Bereit",
+ "voiceLibrary.summary.label": "Voice library status",
+ "voiceLibrary.summary.profiles": "saved personalities",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} compatible model ready",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} compatible models ready",
+ "voiceLibrary.summary.noModels": "No compatible model installed",
+ "voiceLibrary.search.label": "Search voice profiles",
+ "voiceLibrary.search.placeholder": "Search voices, languages, or transcripts",
+ "voiceLibrary.filters.language": "Filter by language",
+ "voiceLibrary.filters.allLanguages": "All languages",
+ "voiceLibrary.empty.title": "No speech voices yet",
+ "voiceLibrary.empty.body": "Record or upload a consented reference clip, add its exact transcript, and reuse the voice across compatible text-to-speech models.",
+ "voiceLibrary.noResults.title": "No voices match these filters",
+ "voiceLibrary.noResults.body": "Try a different search or show every language.",
+ "voiceLibrary.detail.eyebrow": "Reference recording and exact transcript",
+ "voiceLibrary.detail.close": "Schließen",
+ "voiceLibrary.detail.emptyTitle": "Select a voice to inspect it",
+ "voiceLibrary.detail.emptyBody": "Reference audio, transcript, compatibility, and consent details will appear here.",
+ "voiceLibrary.detail.referenceAudio": "Reference audio",
+ "voiceLibrary.detail.transcript": "Reference transcript",
+ "voiceLibrary.metadata.language": "Sprache",
+ "voiceLibrary.metadata.languageUnknown": "Not specified",
+ "voiceLibrary.metadata.duration": "Duration",
+ "voiceLibrary.metadata.sampleRate": "Sample rate",
+ "voiceLibrary.metadata.created": "Erstellt",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consent confirmed",
+ "voiceLibrary.consent.confirmedAt": "Confirmed {{date}}",
+ "voiceLibrary.modelSetup.title": "Install a voice-cloning model",
+ "voiceLibrary.modelSetup.body": "This voice is ready, but it needs a model that accepts reference audio. Choose one verified by this server.",
+ "voiceLibrary.modelSetup.loading": "Finding compatible models in your galleries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selected during install",
+ "voiceLibrary.modelSetup.install": "Installieren",
+ "voiceLibrary.modelSetup.installing": "Wird installiert…",
+ "voiceLibrary.modelSetup.loadFailed": "Compatible recommendations could not be loaded.",
+ "voiceLibrary.modelSetup.noneAvailable": "No compatible uninstalled model is available in the configured galleries.",
+ "voiceLibrary.modelSetup.capabilityNote": "Recommendations come from model capability metadata, not a browser-side model list.",
+ "voiceLibrary.api.title": "API usage and compatible models",
+ "voiceLibrary.api.summary": "Use this voice outside the browser or check model support",
+ "voiceLibrary.api.body": "Send the profile’s stable voice URI with each speech request. LocalAI resolves the private reference audio and transcript at generation time; the settings of your models are not changed.",
+ "voiceLibrary.api.compatibleModels": "Compatible installed models",
+ "voiceLibrary.api.noInstalledModels": "None yet. Install one of the server-verified models above before sending this request.",
+ "voiceLibrary.api.curlExample": "cURL example",
+ "voiceLibrary.api.copy": "Copy request",
+ "voiceLibrary.api.endpointNote": "The same model, input, and voice fields also work with POST /tts.",
+ "voiceLibrary.actions.create": "Add a speech voice",
+ "voiceLibrary.actions.createFirst": "Add your first speech voice",
+ "voiceLibrary.actions.retry": "Erneut versuchen",
+ "voiceLibrary.actions.clearFilters": "Filter zurücksetzen",
+ "voiceLibrary.actions.useInTTS": "Use in Text to Speech",
+ "voiceLibrary.actions.delete": "Delete voice",
+ "voiceLibrary.actions.installModel": "Install a compatible model",
+ "voiceLibrary.actions.browseModels": "Browse all models",
+ "voiceLibrary.deleteDialog.title": "Delete this voice?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” and its reference audio will be permanently removed. Models set up with this voice are not changed.",
+ "voiceLibrary.deleteDialog.deleting": "Deleting…",
+ "voiceLibrary.toasts.deleted": "Deleted {{name}}",
+ "voiceLibrary.toasts.installStarted": "Installing {{name}}. Progress is available in Operations.",
+ "voiceLibrary.toasts.installFailed": "Could not start installation: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "API request copied",
+ "voiceLibrary.toasts.copyFailed": "Could not copy the API request",
+ "voiceCreate.title": "Add a speech voice",
+ "voiceCreate.subtitle": "A speech voice is one or more clean reference clips and their exact transcripts. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "Entfernen",
+ "voiceCreate.sections.reference.title": "Reference audio",
+ "voiceCreate.sections.reference.body": "Record in the browser or upload an existing clip. Speech should be clean, natural, and from one speaker.",
+ "voiceCreate.sections.details.title": "Identity and transcript",
+ "voiceCreate.sections.details.body": "Give admins a recognizable name and transcribe the clip exactly, including fillers and punctuation.",
+ "voiceCreate.sections.consent.title": "Authorization",
+ "voiceCreate.sections.consent.body": "Voice cloning can be sensitive. Confirm permission before this reference is saved.",
+ "voiceCreate.audio.label": "Voice reference",
+ "voiceCreate.audio.normalizing": "Preparing a secure PCM WAV…",
+ "voiceCreate.audio.preview": "Normalized reference",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "Reference audio must be between 1 second and 2 minutes.",
+ "voiceCreate.audio.decodeError": "This browser could not decode the selected audio file.",
+ "voiceCreate.audio.qualityReady": "Recommended length",
+ "voiceCreate.audio.qualityHint": "6–30 seconds usually produces a stronger clone",
+ "voiceCreate.fields.name": "Voice name",
+ "voiceCreate.fields.namePlaceholder": "e.g. Documentary narrator",
+ "voiceCreate.fields.language": "Language (optional)",
+ "voiceCreate.fields.languagePlaceholder": "e.g. en-US",
+ "voiceCreate.fields.description": "Description (optional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tone, source, or intended use",
+ "voiceCreate.fields.transcript": "Exact transcript",
+ "voiceCreate.fields.transcriptPlaceholder": "Type every word spoken in the reference clip…",
+ "voiceCreate.fields.transcriptHint": "Match the recording exactly. Do not rewrite grammar or remove pauses and fillers.",
+ "voiceCreate.consent.title": "I confirm that this voice may be cloned",
+ "voiceCreate.consent.body": "I have the speaker’s permission or another lawful basis to store and use this recording for synthesized speech.",
+ "voiceCreate.readiness.title": "Profile readiness",
+ "voiceCreate.readiness.body": "A voice becomes available in Text to Speech when every required item is complete.",
+ "voiceCreate.readiness.audio": "Valid PCM-WAV reference",
+ "voiceCreate.readiness.quality": "6–30 second quality window",
+ "voiceCreate.readiness.name": "Recognizable voice name",
+ "voiceCreate.readiness.transcript": "Exact reference transcript",
+ "voiceCreate.readiness.consent": "Authorization confirmed",
+ "voiceCreate.privacy.title": "Private by default. ",
+ "voiceCreate.privacy.body": "Reference audio is stored in LocalAI’s data directory with restricted file permissions and is never exposed as a model-folder path.",
+ "voiceCreate.actions.back": "Back to library",
+ "voiceCreate.actions.cancel": "Abbrechen",
+ "voiceCreate.actions.save": "Save voice",
+ "voiceCreate.actions.saving": "Saving…",
+ "voiceCreate.toasts.created": "{{name}} is ready to use",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Entfernen",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Abbrechen",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Stopp",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Vorschau",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Erneut versuchen",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installieren",
+ "needed.installing": "Wird installiert",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/en.js b/addons/voice/ui/src/i18n/voice.en.json
similarity index 95%
rename from addons/voice/ui/src/i18n/en.js
rename to addons/voice/ui/src/i18n/voice.en.json
index 037e6fcd22..32a1583d41 100644
--- a/addons/voice/ui/src/i18n/en.js
+++ b/addons/voice/ui/src/i18n/voice.en.json
@@ -1,5 +1,4 @@
-// Generated once from the host locale files; edit by hand from here on.
-export default {
+{
"voice.adminOnly.title": "Administrators only",
"voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
"speech.title": "Speak text",
@@ -68,25 +67,15 @@ export default {
"voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
"voice.moreTitle": "More tools",
"voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
- "voice.why": [
- "One speaker is assumed. A clip with two people mixes both voices.",
- "A different microphone or room changes how a voice sounds.",
- "This is a similarity score, not a security check. Do not use it to unlock anything."
- ],
- "voice.stored": [
- {
- "where": "On the server",
- "what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept."
- },
- {
- "where": "In this browser",
- "what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling."
- },
- {
- "where": "Clips you test with",
- "what": "They go to the model on this server to be compared and are not kept."
- }
- ],
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
"cutoff.label": "Cut-off",
"cutoff.default": "model default",
"cutoff.reset": "Reset to {{value}}",
diff --git a/addons/voice/ui/src/i18n/voice.es.json b/addons/voice/ui/src/i18n/voice.es.json
new file mode 100644
index 0000000000..eb32ceda07
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.es.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voice",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "Texto",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "Resultado",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "ID de voz opcional",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "Voces",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "Reconocimiento",
+ "voices.tab.library": "Biblioteca",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "En marcha",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nombre",
+ "known.added": "Añadido",
+ "known.actions": "Acciones",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Deshacer",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Quitar {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "Cerrar",
+ "enrol.cancel": "Cancelar",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nombre",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "Texto a voz",
+ "tts.labels.model": "Modelo",
+ "tts.labels.voice": "Voz",
+ "tts.labels.voicePlaceholder": "ID de voz opcional",
+ "tts.labels.input": "Texto",
+ "tts.labels.inputPlaceholder": "Introduce el texto a sintetizar...",
+ "tts.labels.instructions": "Instrucciones",
+ "tts.labels.instructionsPlaceholder": "Por ejemplo: Habla despacio y con calidez",
+ "tts.labels.instructionsHint": "Indicaciones opcionales sobre la locución. La compatibilidad depende del backend.",
+ "tts.actions.generate": "Generar audio",
+ "tts.actions.generating": "Generando...",
+ "tts.empty": "El audio generado aparecerá aquí",
+ "tts.toasts.noText": "Por favor introduce texto",
+ "tts.toasts.noModel": "Por favor selecciona un modelo",
+ "tts.toasts.generated": "Speech generated",
+ "tts.toasts.generateFailed": "Generación fallida",
+ "tts.voiceLibrary.cloningReady": "Voice cloning",
+ "tts.voiceLibrary.loading": "Loading saved personalities…",
+ "tts.voiceLibrary.modelDefault": "Use model default",
+ "tts.voiceLibrary.empty": "No saved personalities yet.",
+ "tts.voiceLibrary.create": "Create one",
+ "tts.voiceLibrary.manage": "Manage Personality Library",
+ "tts.voiceLibrary.namedVoiceHint": "This model does not accept reference-audio profiles. You can still enter a named speaker supported by its backend.",
+ "voiceLibrary.title": "Speech voices",
+ "voiceLibrary.subtitle": "A recording and its exact transcript. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Loading saved personalities…",
+ "voiceLibrary.listLabel": "Saved voice personalities",
+ "voiceLibrary.status.ready": "Listo",
+ "voiceLibrary.summary.label": "Voice library status",
+ "voiceLibrary.summary.profiles": "saved personalities",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} compatible model ready",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} compatible models ready",
+ "voiceLibrary.summary.noModels": "No compatible model installed",
+ "voiceLibrary.search.label": "Search voice profiles",
+ "voiceLibrary.search.placeholder": "Search voices, languages, or transcripts",
+ "voiceLibrary.filters.language": "Filter by language",
+ "voiceLibrary.filters.allLanguages": "All languages",
+ "voiceLibrary.empty.title": "No speech voices yet",
+ "voiceLibrary.empty.body": "Record or upload a consented reference clip, add its exact transcript, and reuse the voice across compatible text-to-speech models.",
+ "voiceLibrary.noResults.title": "No voices match these filters",
+ "voiceLibrary.noResults.body": "Try a different search or show every language.",
+ "voiceLibrary.detail.eyebrow": "Reference recording and exact transcript",
+ "voiceLibrary.detail.close": "Cerrar",
+ "voiceLibrary.detail.emptyTitle": "Select a voice to inspect it",
+ "voiceLibrary.detail.emptyBody": "Reference audio, transcript, compatibility, and consent details will appear here.",
+ "voiceLibrary.detail.referenceAudio": "Reference audio",
+ "voiceLibrary.detail.transcript": "Reference transcript",
+ "voiceLibrary.metadata.language": "Idioma",
+ "voiceLibrary.metadata.languageUnknown": "Not specified",
+ "voiceLibrary.metadata.duration": "Duration",
+ "voiceLibrary.metadata.sampleRate": "Sample rate",
+ "voiceLibrary.metadata.created": "Creado",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consent confirmed",
+ "voiceLibrary.consent.confirmedAt": "Confirmed {{date}}",
+ "voiceLibrary.modelSetup.title": "Install a voice-cloning model",
+ "voiceLibrary.modelSetup.body": "This voice is ready, but it needs a model that accepts reference audio. Choose one verified by this server.",
+ "voiceLibrary.modelSetup.loading": "Finding compatible models in your galleries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selected during install",
+ "voiceLibrary.modelSetup.install": "Instalar",
+ "voiceLibrary.modelSetup.installing": "Instalando…",
+ "voiceLibrary.modelSetup.loadFailed": "Compatible recommendations could not be loaded.",
+ "voiceLibrary.modelSetup.noneAvailable": "No compatible uninstalled model is available in the configured galleries.",
+ "voiceLibrary.modelSetup.capabilityNote": "Recommendations come from model capability metadata, not a browser-side model list.",
+ "voiceLibrary.api.title": "API usage and compatible models",
+ "voiceLibrary.api.summary": "Use this voice outside the browser or check model support",
+ "voiceLibrary.api.body": "Send the profile’s stable voice URI with each speech request. LocalAI resolves the private reference audio and transcript at generation time; the settings of your models are not changed.",
+ "voiceLibrary.api.compatibleModels": "Compatible installed models",
+ "voiceLibrary.api.noInstalledModels": "None yet. Install one of the server-verified models above before sending this request.",
+ "voiceLibrary.api.curlExample": "cURL example",
+ "voiceLibrary.api.copy": "Copy request",
+ "voiceLibrary.api.endpointNote": "The same model, input, and voice fields also work with POST /tts.",
+ "voiceLibrary.actions.create": "Add a speech voice",
+ "voiceLibrary.actions.createFirst": "Add your first speech voice",
+ "voiceLibrary.actions.retry": "Reintentar",
+ "voiceLibrary.actions.clearFilters": "Limpiar filtros",
+ "voiceLibrary.actions.useInTTS": "Use in Text to Speech",
+ "voiceLibrary.actions.delete": "Delete voice",
+ "voiceLibrary.actions.installModel": "Install a compatible model",
+ "voiceLibrary.actions.browseModels": "Browse all models",
+ "voiceLibrary.deleteDialog.title": "Delete this voice?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” and its reference audio will be permanently removed. Models set up with this voice are not changed.",
+ "voiceLibrary.deleteDialog.deleting": "Deleting…",
+ "voiceLibrary.toasts.deleted": "Deleted {{name}}",
+ "voiceLibrary.toasts.installStarted": "Installing {{name}}. Progress is available in Operations.",
+ "voiceLibrary.toasts.installFailed": "Could not start installation: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "API request copied",
+ "voiceLibrary.toasts.copyFailed": "Could not copy the API request",
+ "voiceCreate.title": "Add a speech voice",
+ "voiceCreate.subtitle": "A speech voice is one or more clean reference clips and their exact transcripts. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "Quitar",
+ "voiceCreate.sections.reference.title": "Reference audio",
+ "voiceCreate.sections.reference.body": "Record in the browser or upload an existing clip. Speech should be clean, natural, and from one speaker.",
+ "voiceCreate.sections.details.title": "Identity and transcript",
+ "voiceCreate.sections.details.body": "Give admins a recognizable name and transcribe the clip exactly, including fillers and punctuation.",
+ "voiceCreate.sections.consent.title": "Authorization",
+ "voiceCreate.sections.consent.body": "Voice cloning can be sensitive. Confirm permission before this reference is saved.",
+ "voiceCreate.audio.label": "Voice reference",
+ "voiceCreate.audio.normalizing": "Preparing a secure PCM WAV…",
+ "voiceCreate.audio.preview": "Normalized reference",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "Reference audio must be between 1 second and 2 minutes.",
+ "voiceCreate.audio.decodeError": "This browser could not decode the selected audio file.",
+ "voiceCreate.audio.qualityReady": "Recommended length",
+ "voiceCreate.audio.qualityHint": "6–30 seconds usually produces a stronger clone",
+ "voiceCreate.fields.name": "Voice name",
+ "voiceCreate.fields.namePlaceholder": "e.g. Documentary narrator",
+ "voiceCreate.fields.language": "Language (optional)",
+ "voiceCreate.fields.languagePlaceholder": "e.g. en-US",
+ "voiceCreate.fields.description": "Description (optional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tone, source, or intended use",
+ "voiceCreate.fields.transcript": "Exact transcript",
+ "voiceCreate.fields.transcriptPlaceholder": "Type every word spoken in the reference clip…",
+ "voiceCreate.fields.transcriptHint": "Match the recording exactly. Do not rewrite grammar or remove pauses and fillers.",
+ "voiceCreate.consent.title": "I confirm that this voice may be cloned",
+ "voiceCreate.consent.body": "I have the speaker’s permission or another lawful basis to store and use this recording for synthesized speech.",
+ "voiceCreate.readiness.title": "Profile readiness",
+ "voiceCreate.readiness.body": "A voice becomes available in Text to Speech when every required item is complete.",
+ "voiceCreate.readiness.audio": "Valid PCM-WAV reference",
+ "voiceCreate.readiness.quality": "6–30 second quality window",
+ "voiceCreate.readiness.name": "Recognizable voice name",
+ "voiceCreate.readiness.transcript": "Exact reference transcript",
+ "voiceCreate.readiness.consent": "Authorization confirmed",
+ "voiceCreate.privacy.title": "Private by default. ",
+ "voiceCreate.privacy.body": "Reference audio is stored in LocalAI’s data directory with restricted file permissions and is never exposed as a model-folder path.",
+ "voiceCreate.actions.back": "Back to library",
+ "voiceCreate.actions.cancel": "Cancelar",
+ "voiceCreate.actions.save": "Save voice",
+ "voiceCreate.actions.saving": "Saving…",
+ "voiceCreate.toasts.created": "{{name}} is ready to use",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Quitar",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Cancelar",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Detener",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Vista previa",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Reintentar",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Instalar",
+ "needed.installing": "Instalando",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/voice.fr.json b/addons/voice/ui/src/i18n/voice.fr.json
new file mode 100644
index 0000000000..b450bf4a95
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.fr.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voix",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "Texte",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "Résultat",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "Identifiant de locuteur ou de voix (facultatif)",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "Voix",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "Reconnaissance",
+ "voices.tab.library": "Bibliothèque",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "En cours",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nom",
+ "known.added": "Ajouté",
+ "known.actions": "Actions",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Annuler",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Retirer {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "Fermer",
+ "enrol.cancel": "Annuler",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Enregistrement",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Enregistrement",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nom",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Transcription exacte",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "Synthèse vocale",
+ "tts.labels.model": "Modèle",
+ "tts.labels.voice": "Voix",
+ "tts.labels.voicePlaceholder": "Identifiant de locuteur ou de voix (facultatif)",
+ "tts.labels.input": "Texte",
+ "tts.labels.inputPlaceholder": "Saisissez le texte à synthétiser...",
+ "tts.labels.instructions": "Instructions",
+ "tts.labels.instructionsPlaceholder": "Par exemple : Parle lentement et chaleureusement",
+ "tts.labels.instructionsHint": "Indications de diction facultatives. La prise en charge dépend du backend.",
+ "tts.actions.generate": "Générer",
+ "tts.actions.generating": "Génération...",
+ "tts.empty": "L'audio généré apparaîtra ici",
+ "tts.toasts.noText": "Veuillez saisir du texte",
+ "tts.toasts.noModel": "Veuillez sélectionner un modèle",
+ "tts.toasts.generated": "Parole générée",
+ "tts.toasts.generateFailed": "Échec de la génération",
+ "tts.voiceLibrary.cloningReady": "Clonage de voix",
+ "tts.voiceLibrary.loading": "Chargement des voix enregistrées…",
+ "tts.voiceLibrary.modelDefault": "Utiliser la voix par défaut du modèle",
+ "tts.voiceLibrary.empty": "Aucune voix enregistrée pour l'instant.",
+ "tts.voiceLibrary.create": "En créer une",
+ "tts.voiceLibrary.manage": "Gérer la bibliothèque de voix",
+ "tts.voiceLibrary.namedVoiceHint": "Ce modèle n'accepte pas les profils audio de référence. Vous pouvez toujours saisir un locuteur nommé pris en charge par son backend.",
+ "voiceLibrary.title": "Bibliothèque de voix",
+ "voiceLibrary.subtitle": "Créez et gérez des voix réutilisables à partir d'un ou plusieurs enregistrements de référence.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Chargement des voix enregistrées…",
+ "voiceLibrary.listLabel": "Voix enregistrées",
+ "voiceLibrary.status.ready": "Prêt",
+ "voiceLibrary.summary.label": "État de la bibliothèque de voix",
+ "voiceLibrary.summary.profiles": "voix enregistrées",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} modèle compatible prêt",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} modèles compatibles prêts",
+ "voiceLibrary.summary.noModels": "Aucun modèle compatible installé",
+ "voiceLibrary.search.label": "Rechercher des voix",
+ "voiceLibrary.search.placeholder": "Rechercher par voix, langue ou transcription",
+ "voiceLibrary.filters.language": "Filtrer par langue",
+ "voiceLibrary.filters.allLanguages": "Toutes les langues",
+ "voiceLibrary.empty.title": "Votre bibliothèque de voix est vide",
+ "voiceLibrary.empty.body": "Enregistrez ou importez des extraits de référence avec consentement, ajoutez leur transcription exacte et réutilisez la voix avec les modèles TTS compatibles.",
+ "voiceLibrary.noResults.title": "Aucune voix ne correspond à ces filtres",
+ "voiceLibrary.noResults.body": "Essayez une autre recherche ou affichez toutes les langues.",
+ "voiceLibrary.detail.eyebrow": "Voix sélectionnée",
+ "voiceLibrary.detail.close": "Fermer",
+ "voiceLibrary.detail.emptyTitle": "Sélectionnez une voix pour l'inspecter",
+ "voiceLibrary.detail.emptyBody": "L'audio de référence, la transcription, la compatibilité et le consentement s'afficheront ici.",
+ "voiceLibrary.detail.referenceAudio": "Audio de référence",
+ "voiceLibrary.detail.transcript": "Transcription de référence",
+ "voiceLibrary.metadata.language": "Langue",
+ "voiceLibrary.metadata.languageUnknown": "Non précisée",
+ "voiceLibrary.metadata.duration": "Durée",
+ "voiceLibrary.metadata.sampleRate": "Fréquence d'échantillonnage",
+ "voiceLibrary.metadata.created": "Créée le",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consentement confirmé",
+ "voiceLibrary.consent.confirmedAt": "Confirmé le {{date}}",
+ "voiceLibrary.modelSetup.title": "Installer un modèle de clonage de voix",
+ "voiceLibrary.modelSetup.body": "Cette voix est prête, mais elle a besoin d'un modèle acceptant l'audio de référence. Choisissez-en un vérifié par ce serveur.",
+ "voiceLibrary.modelSetup.loading": "Recherche de modèles compatibles dans vos galeries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend choisi lors de l'installation",
+ "voiceLibrary.modelSetup.install": "Installer",
+ "voiceLibrary.modelSetup.installing": "Installation…",
+ "voiceLibrary.modelSetup.loadFailed": "Les recommandations compatibles n'ont pas pu être chargées.",
+ "voiceLibrary.modelSetup.noneAvailable": "Aucun modèle compatible non installé n'est disponible dans les galeries configurées.",
+ "voiceLibrary.modelSetup.capabilityNote": "Les recommandations proviennent des métadonnées de capacités des modèles, pas d'une liste côté navigateur.",
+ "voiceLibrary.api.title": "Utilisation de l'API et modèles compatibles",
+ "voiceLibrary.api.summary": "Utilisez cette voix hors du navigateur ou vérifiez la compatibilité des modèles",
+ "voiceLibrary.api.body": "Envoyez l'URI stable de la voix avec chaque requête de synthèse. LocalAI résout l'audio de référence privé et sa transcription au moment de la génération ; les réglages de vos modèles ne sont pas modifiés.",
+ "voiceLibrary.api.compatibleModels": "Modèles installés compatibles",
+ "voiceLibrary.api.noInstalledModels": "Aucun pour l'instant. Installez l'un des modèles vérifiés ci-dessus avant d'envoyer cette requête.",
+ "voiceLibrary.api.curlExample": "Exemple cURL",
+ "voiceLibrary.api.copy": "Copier la requête",
+ "voiceLibrary.api.endpointNote": "Les mêmes champs model, input et voice fonctionnent aussi avec POST /tts.",
+ "voiceLibrary.actions.create": "Créer une voix",
+ "voiceLibrary.actions.createFirst": "Créer votre première voix",
+ "voiceLibrary.actions.retry": "Réessayer",
+ "voiceLibrary.actions.clearFilters": "Effacer les filtres",
+ "voiceLibrary.actions.useInTTS": "Utiliser en synthèse vocale",
+ "voiceLibrary.actions.delete": "Supprimer la voix",
+ "voiceLibrary.actions.installModel": "Installer un modèle compatible",
+ "voiceLibrary.actions.browseModels": "Parcourir tous les modèles",
+ "voiceLibrary.deleteDialog.title": "Supprimer cette voix ?",
+ "voiceLibrary.deleteDialog.message": "« {{name}} » et son audio de référence seront définitivement supprimés. Les modèles configurés avec cette voix ne sont pas modifiés.",
+ "voiceLibrary.deleteDialog.deleting": "Suppression…",
+ "voiceLibrary.toasts.deleted": "{{name}} supprimée",
+ "voiceLibrary.toasts.installStarted": "Installation de {{name}}. La progression est disponible dans Opérations.",
+ "voiceLibrary.toasts.installFailed": "Impossible de démarrer l'installation : {{message}}",
+ "voiceLibrary.toasts.apiCopied": "Requête API copiée",
+ "voiceLibrary.toasts.copyFailed": "Impossible de copier la requête API",
+ "voiceCreate.title": "Créer une voix réutilisable",
+ "voiceCreate.subtitle": "Créez une voix réutilisable à partir d'un ou plusieurs extraits de référence propres et de leur transcription exacte.",
+ "voiceCreate.references.add": "Ajouter une autre référence",
+ "voiceCreate.references.additional": "Référence {{number}}",
+ "voiceCreate.references.remove": "Retirer",
+ "voiceCreate.sections.reference.title": "Audio de référence",
+ "voiceCreate.sections.reference.body": "Enregistrez dans le navigateur ou importez un extrait existant. La parole doit être propre, naturelle et provenir d'un seul locuteur.",
+ "voiceCreate.sections.details.title": "Identité et transcription",
+ "voiceCreate.sections.details.body": "Donnez un nom reconnaissable aux administrateurs et transcrivez l'extrait exactement, hésitations et ponctuation comprises.",
+ "voiceCreate.sections.consent.title": "Autorisation",
+ "voiceCreate.sections.consent.body": "Le clonage de voix peut être sensible. Confirmez l'autorisation avant d'enregistrer cette référence.",
+ "voiceCreate.audio.label": "Référence vocale",
+ "voiceCreate.audio.normalizing": "Préparation d'un WAV PCM sécurisé…",
+ "voiceCreate.audio.preview": "Référence normalisée",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "L'audio de référence doit durer entre 1 seconde et 2 minutes.",
+ "voiceCreate.audio.decodeError": "Ce navigateur n'a pas pu décoder le fichier audio sélectionné.",
+ "voiceCreate.audio.qualityReady": "Durée recommandée",
+ "voiceCreate.audio.qualityHint": "6 à 30 secondes produisent généralement un meilleur clone",
+ "voiceCreate.fields.name": "Nom de la voix",
+ "voiceCreate.fields.namePlaceholder": "ex. Narrateur documentaire",
+ "voiceCreate.fields.language": "Langue (facultatif)",
+ "voiceCreate.fields.languagePlaceholder": "ex. fr-FR",
+ "voiceCreate.fields.description": "Description (facultatif)",
+ "voiceCreate.fields.descriptionPlaceholder": "Ton, source ou usage prévu",
+ "voiceCreate.fields.transcript": "Transcription exacte",
+ "voiceCreate.fields.transcriptPlaceholder": "Saisissez chaque mot prononcé dans l'extrait…",
+ "voiceCreate.fields.transcriptHint": "Reproduisez l'enregistrement exactement. Ne corrigez pas la grammaire et ne supprimez ni pauses ni hésitations.",
+ "voiceCreate.consent.title": "Je confirme que cette voix peut être clonée",
+ "voiceCreate.consent.body": "J'ai l'autorisation du locuteur ou une autre base légale pour stocker et utiliser cet enregistrement afin de produire de la parole synthétique.",
+ "voiceCreate.readiness.title": "Préparation de la voix",
+ "voiceCreate.readiness.body": "Une voix devient disponible en synthèse vocale lorsque tous les éléments requis sont remplis.",
+ "voiceCreate.readiness.audio": "Référence PCM-WAV valide",
+ "voiceCreate.readiness.quality": "Fenêtre de qualité de 6 à 30 secondes",
+ "voiceCreate.readiness.name": "Nom de voix reconnaissable",
+ "voiceCreate.readiness.transcript": "Transcription de référence exacte",
+ "voiceCreate.readiness.consent": "Autorisation confirmée",
+ "voiceCreate.privacy.title": "Privé par défaut. ",
+ "voiceCreate.privacy.body": "L'audio de référence est stocké dans le répertoire de données de LocalAI avec des permissions restreintes et n'est jamais exposé comme chemin de dossier de modèle.",
+ "voiceCreate.actions.back": "Retour à la bibliothèque",
+ "voiceCreate.actions.cancel": "Annuler",
+ "voiceCreate.actions.save": "Enregistrer la voix",
+ "voiceCreate.actions.saving": "Enregistrement…",
+ "voiceCreate.toasts.created": "{{name}} est prête à l'emploi",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Enregistrer",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Retirer",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Annuler",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Arrêter",
+ "clip.start": "Démarrer l'enregistrement",
+ "clip.recorded": "Enregistrement",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Aperçu",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Réessayer",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installer",
+ "needed.installing": "Installation en cours",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Parcourir tous les modèles",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/voice.id.json b/addons/voice/ui/src/i18n/voice.id.json
new file mode 100644
index 0000000000..b3c7d9fb49
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.id.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Suara",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "Teks",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "Hasil",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "ID suara opsional",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "Suara",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "Pengenalan",
+ "voices.tab.library": "Pustaka",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "Sedang dikerjakan",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nama",
+ "known.added": "Ditambahkan",
+ "known.actions": "Aksi",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Urungkan",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Hapus {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "Tutup",
+ "enrol.cancel": "Batal",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nama",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "Teks ke Suara",
+ "tts.labels.model": "Model",
+ "tts.labels.voice": "Suara",
+ "tts.labels.voicePlaceholder": "ID suara opsional",
+ "tts.labels.input": "Teks",
+ "tts.labels.inputPlaceholder": "Masukkan teks untuk disintesis...",
+ "tts.labels.instructions": "Instruksi",
+ "tts.labels.instructionsPlaceholder": "Contoh: Ucapkan perlahan dan hangat",
+ "tts.labels.instructionsHint": "Panduan penyampaian opsional. Dukungan bergantung pada backend.",
+ "tts.actions.generate": "Hasilkan",
+ "tts.actions.generating": "Menghasilkan...",
+ "tts.empty": "Audio yang dihasilkan akan muncul di sini",
+ "tts.toasts.noText": "Silahkan masukkan teks",
+ "tts.toasts.noModel": "Silahkan pilih model",
+ "tts.toasts.generated": "Speech generated",
+ "tts.toasts.generateFailed": "Gagal menghasilkan",
+ "tts.voiceLibrary.cloningReady": "Voice cloning",
+ "tts.voiceLibrary.loading": "Loading saved personalities…",
+ "tts.voiceLibrary.modelDefault": "Use model default",
+ "tts.voiceLibrary.empty": "No saved personalities yet.",
+ "tts.voiceLibrary.create": "Create one",
+ "tts.voiceLibrary.manage": "Manage Personality Library",
+ "tts.voiceLibrary.namedVoiceHint": "This model does not accept reference-audio profiles. You can still enter a named speaker supported by its backend.",
+ "voiceLibrary.title": "Speech voices",
+ "voiceLibrary.subtitle": "A recording and its exact transcript. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Loading saved personalities…",
+ "voiceLibrary.listLabel": "Saved voice personalities",
+ "voiceLibrary.status.ready": "Siap",
+ "voiceLibrary.summary.label": "Voice library status",
+ "voiceLibrary.summary.profiles": "saved personalities",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} compatible model ready",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} compatible models ready",
+ "voiceLibrary.summary.noModels": "No compatible model installed",
+ "voiceLibrary.search.label": "Search voice profiles",
+ "voiceLibrary.search.placeholder": "Search voices, languages, or transcripts",
+ "voiceLibrary.filters.language": "Filter by language",
+ "voiceLibrary.filters.allLanguages": "All languages",
+ "voiceLibrary.empty.title": "No speech voices yet",
+ "voiceLibrary.empty.body": "Record or upload a consented reference clip, add its exact transcript, and reuse the voice across compatible text-to-speech models.",
+ "voiceLibrary.noResults.title": "No voices match these filters",
+ "voiceLibrary.noResults.body": "Try a different search or show every language.",
+ "voiceLibrary.detail.eyebrow": "Reference recording and exact transcript",
+ "voiceLibrary.detail.close": "Tutup",
+ "voiceLibrary.detail.emptyTitle": "Select a voice to inspect it",
+ "voiceLibrary.detail.emptyBody": "Reference audio, transcript, compatibility, and consent details will appear here.",
+ "voiceLibrary.detail.referenceAudio": "Reference audio",
+ "voiceLibrary.detail.transcript": "Reference transcript",
+ "voiceLibrary.metadata.language": "Bahasa",
+ "voiceLibrary.metadata.languageUnknown": "Not specified",
+ "voiceLibrary.metadata.duration": "Duration",
+ "voiceLibrary.metadata.sampleRate": "Sample rate",
+ "voiceLibrary.metadata.created": "Membuat",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consent confirmed",
+ "voiceLibrary.consent.confirmedAt": "Confirmed {{date}}",
+ "voiceLibrary.modelSetup.title": "Install a voice-cloning model",
+ "voiceLibrary.modelSetup.body": "This voice is ready, but it needs a model that accepts reference audio. Choose one verified by this server.",
+ "voiceLibrary.modelSetup.loading": "Finding compatible models in your galleries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selected during install",
+ "voiceLibrary.modelSetup.install": "Pasang",
+ "voiceLibrary.modelSetup.installing": "Memasang…",
+ "voiceLibrary.modelSetup.loadFailed": "Compatible recommendations could not be loaded.",
+ "voiceLibrary.modelSetup.noneAvailable": "No compatible uninstalled model is available in the configured galleries.",
+ "voiceLibrary.modelSetup.capabilityNote": "Recommendations come from model capability metadata, not a browser-side model list.",
+ "voiceLibrary.api.title": "API usage and compatible models",
+ "voiceLibrary.api.summary": "Use this voice outside the browser or check model support",
+ "voiceLibrary.api.body": "Send the profile’s stable voice URI with each speech request. LocalAI resolves the private reference audio and transcript at generation time; the settings of your models are not changed.",
+ "voiceLibrary.api.compatibleModels": "Compatible installed models",
+ "voiceLibrary.api.noInstalledModels": "None yet. Install one of the server-verified models above before sending this request.",
+ "voiceLibrary.api.curlExample": "cURL example",
+ "voiceLibrary.api.copy": "Copy request",
+ "voiceLibrary.api.endpointNote": "The same model, input, and voice fields also work with POST /tts.",
+ "voiceLibrary.actions.create": "Add a speech voice",
+ "voiceLibrary.actions.createFirst": "Add your first speech voice",
+ "voiceLibrary.actions.retry": "Coba lagi",
+ "voiceLibrary.actions.clearFilters": "Hapus filter",
+ "voiceLibrary.actions.useInTTS": "Use in Text to Speech",
+ "voiceLibrary.actions.delete": "Delete voice",
+ "voiceLibrary.actions.installModel": "Install a compatible model",
+ "voiceLibrary.actions.browseModels": "Browse all models",
+ "voiceLibrary.deleteDialog.title": "Delete this voice?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” and its reference audio will be permanently removed. Models set up with this voice are not changed.",
+ "voiceLibrary.deleteDialog.deleting": "Deleting…",
+ "voiceLibrary.toasts.deleted": "Deleted {{name}}",
+ "voiceLibrary.toasts.installStarted": "Installing {{name}}. Progress is available in Operations.",
+ "voiceLibrary.toasts.installFailed": "Could not start installation: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "API request copied",
+ "voiceLibrary.toasts.copyFailed": "Could not copy the API request",
+ "voiceCreate.title": "Add a speech voice",
+ "voiceCreate.subtitle": "A speech voice is one or more clean reference clips and their exact transcripts. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "Hapus",
+ "voiceCreate.sections.reference.title": "Reference audio",
+ "voiceCreate.sections.reference.body": "Record in the browser or upload an existing clip. Speech should be clean, natural, and from one speaker.",
+ "voiceCreate.sections.details.title": "Identity and transcript",
+ "voiceCreate.sections.details.body": "Give admins a recognizable name and transcribe the clip exactly, including fillers and punctuation.",
+ "voiceCreate.sections.consent.title": "Authorization",
+ "voiceCreate.sections.consent.body": "Voice cloning can be sensitive. Confirm permission before this reference is saved.",
+ "voiceCreate.audio.label": "Voice reference",
+ "voiceCreate.audio.normalizing": "Preparing a secure PCM WAV…",
+ "voiceCreate.audio.preview": "Normalized reference",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "Reference audio must be between 1 second and 2 minutes.",
+ "voiceCreate.audio.decodeError": "This browser could not decode the selected audio file.",
+ "voiceCreate.audio.qualityReady": "Recommended length",
+ "voiceCreate.audio.qualityHint": "6–30 seconds usually produces a stronger clone",
+ "voiceCreate.fields.name": "Voice name",
+ "voiceCreate.fields.namePlaceholder": "e.g. Documentary narrator",
+ "voiceCreate.fields.language": "Language (optional)",
+ "voiceCreate.fields.languagePlaceholder": "e.g. en-US",
+ "voiceCreate.fields.description": "Description (optional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tone, source, or intended use",
+ "voiceCreate.fields.transcript": "Exact transcript",
+ "voiceCreate.fields.transcriptPlaceholder": "Type every word spoken in the reference clip…",
+ "voiceCreate.fields.transcriptHint": "Match the recording exactly. Do not rewrite grammar or remove pauses and fillers.",
+ "voiceCreate.consent.title": "I confirm that this voice may be cloned",
+ "voiceCreate.consent.body": "I have the speaker’s permission or another lawful basis to store and use this recording for synthesized speech.",
+ "voiceCreate.readiness.title": "Profile readiness",
+ "voiceCreate.readiness.body": "A voice becomes available in Text to Speech when every required item is complete.",
+ "voiceCreate.readiness.audio": "Valid PCM-WAV reference",
+ "voiceCreate.readiness.quality": "6–30 second quality window",
+ "voiceCreate.readiness.name": "Recognizable voice name",
+ "voiceCreate.readiness.transcript": "Exact reference transcript",
+ "voiceCreate.readiness.consent": "Authorization confirmed",
+ "voiceCreate.privacy.title": "Private by default. ",
+ "voiceCreate.privacy.body": "Reference audio is stored in LocalAI’s data directory with restricted file permissions and is never exposed as a model-folder path.",
+ "voiceCreate.actions.back": "Back to library",
+ "voiceCreate.actions.cancel": "Batal",
+ "voiceCreate.actions.save": "Save voice",
+ "voiceCreate.actions.saving": "Saving…",
+ "voiceCreate.toasts.created": "{{name}} is ready to use",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Rekam",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Hapus",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Batal",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Berhenti",
+ "clip.start": "Mulai merekam",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Pratinjau",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Coba lagi",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Pasang",
+ "needed.installing": "Memasang",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/voice.it.json b/addons/voice/ui/src/i18n/voice.it.json
new file mode 100644
index 0000000000..fb3e29241c
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.it.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Solo per amministratori",
+ "voice.adminOnly.text": "Le voci salvate possono essere gestite da un amministratore di questo server. Chiedine uno se devi aggiungere o rimuovere una voce.",
+ "speech.title": "Pronuncia un testo",
+ "speech.lede": "Trasforma un testo in voce con un modello vocale di questo server.",
+ "speech.model": "Model",
+ "speech.voice": "Voice",
+ "speech.defaultVoice": "Voce predefinita",
+ "speech.text": "Testo",
+ "speech.instructions": "Istruzioni (facoltative)",
+ "speech.generate": "Genera la voce",
+ "speech.result": "Risultato",
+ "speech.noModels": "Non è installato nessun modello di sintesi vocale.",
+ "speech.noModelsLink": "Scegline uno dalla galleria dei modelli.",
+ "speech.voicePlaceholder": "ID voce opzionale",
+ "speech.textPlaceholder": "Cosa deve dire?",
+ "speech.noAccess": "Il tuo account non ha accesso alla sintesi vocale. Il server rifiuterà la richiesta.",
+ "voices.title": "Voci",
+ "voices.lede": "Riconosci chi sta parlando e conserva le voci che la sintesi vocale può usare.",
+ "voices.tabs": "Sezioni delle voci",
+ "voices.tab.recognition": "Riconoscimento",
+ "voices.tab.library": "Libreria",
+ "voices.tab.speech": "Sintesi vocale",
+ "voices.tab.recording": "Da una registrazione",
+ "tool.aria": "Strumento di riconoscimento",
+ "tool.sameTab": "Stessa persona?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Foto",
+ "tool.photoA": "Foto A",
+ "tool.photoB": "Foto B",
+ "tool.compare": "Confronta",
+ "tool.comparing": "Confronto in corso",
+ "tool.workingAria": "In corso",
+ "voice.speaker": "parlante",
+ "voice.print": "impronta vocale",
+ "voice.whoTab": "Chi è",
+ "voice.whoLede": "Confronta una clip con le persone che hai registrato.",
+ "voice.sameLede": "Due clip, una domanda. Non serve aver registrato nessuno.",
+ "voice.whoGo": "Identifica il parlante",
+ "voice.working": "Identificazione in corso",
+ "voice.whoNote": "Cerca tra i parlanti registrati su questo server. La clip che usi per il test non viene conservata.",
+ "voice.sameNote": "La risposta viene da una sola impronta vocale per clip. Non viene conservato nulla.",
+ "voice.workingText": "Confronto della clip con le impronte vocali che il server conserva.",
+ "voice.probably": "Probabilmente {{name}}",
+ "voice.matchText": "L'impronta vocale di {{name}} è la più vicina tra {{count}} confrontate, a distanza {{distance}}. Sotto {{cutoff}} si considera la stessa persona.",
+ "voice.nobody": "Nessuno dei registrati ha questa voce",
+ "voice.missText": "La più vicina era {{name}} a distanza {{distance}}, oltre la soglia di {{cutoff}}. Di solito significa un parlante nuovo, oppure una clip troppo breve o rumorosa.",
+ "voice.noneEnrolled": "Non c'è nessuno registrato con cui confrontare",
+ "voice.noneEnrolledText": "Registra prima un parlante, poi identifica una clip rispetto a lui.",
+ "voice.noneReturned": "Il server non ha restituito nessun parlante",
+ "voice.noneReturnedText": "Non conserva impronte vocali create con questo modello. Se il server è stato riavviato, le voci registrate sono perse: registrale di nuovo.",
+ "voice.enrolThis": "Registra questa voce",
+ "voice.same": "Probabilmente la stessa persona",
+ "voice.different": "Probabilmente persone diverse",
+ "voice.knownTitle": "Parlanti noti",
+ "voice.enrol": "Registra un parlante",
+ "voice.enrolTitle": "Registra un parlante",
+ "voice.enrolIntro": "Il server conserva un'impronta vocale e un nome. Non conserva la registrazione.",
+ "voice.emptyTitle": "Nessuno è ancora registrato in questo browser",
+ "voice.emptyText": "Registra un parlante con una breve registrazione, poi prova una clip rispetto a lui.",
+ "voice.agreedHint": "Ho il suo permesso, o un'altra base legittima, per conservare la sua impronta vocale.",
+ "voice.keepHint": "Ti permette di riascoltarla e di registrare di nuovo dopo il riavvio del server. Disattivato per impostazione predefinita. Resta solo in questo browser.",
+ "voice.neededTitle": "Le voci richiedono un modello per i parlanti",
+ "voice.neededText": "Il riconoscimento vocale trasforma una registrazione in un'impronta vocale e confronta le impronte. Funziona con un modello di riconoscimento del parlante, e su questo server non ne è installato nessuno.",
+ "voice.neededWhat": "Installa un modello che supporti il riconoscimento del parlante. Appena ne è installato uno, questa pagina si attiva da sola. Gli utenti hanno bisogno anche del permesso di riconoscimento vocale.",
+ "voice.moreTitle": "Altri strumenti",
+ "voice.moreHint": "stima le caratteristiche di un parlante, ottieni l'impronta vocale grezza",
+ "voice.why.0": "Si presume un solo parlante. Una clip con due persone mescola le due voci.",
+ "voice.why.1": "Un altro microfono o un'altra stanza cambia il suono di una voce.",
+ "voice.why.2": "È un punteggio di somiglianza, non un controllo di sicurezza. Non usarlo per sbloccare nulla.",
+ "voice.stored.0.where": "Sul server",
+ "voice.stored.0.what": "Un'{{print}} (numeri che descrivono la voce, non l'audio), un nome e le tue etichette. Restano in memoria, sono condivise da tutti quelli che usano questo server e si perdono al riavvio. La registrazione non viene conservata.",
+ "voice.stored.1.where": "In questo browser",
+ "voice.stored.1.what": "Nome, etichette e ora di ogni persona che hai registrato. Una copia della registrazione solo se spunti Conserva una copia durante la registrazione.",
+ "voice.stored.2.where": "Clip che usi per il test",
+ "voice.stored.2.what": "Vanno al modello su questo server per essere confrontate e non vengono conservate.",
+ "cutoff.label": "Soglia",
+ "cutoff.default": "predefinita del modello",
+ "cutoff.reset": "Ripristina a {{value}}",
+ "cutoff.stricter": "Più severa",
+ "cutoff.looser": "Più larga",
+ "cutoff.explain": "Più severa significa meno corrispondenze sbagliate e più mancate. La risposta qui sopra si aggiorna mentre la sposti. Il server non viene interpellato di nuovo.",
+ "why.title": "Perché potrebbe essere sbagliato",
+ "why.short": "La clip è breve: {{s}} s. Funziona meglio con almeno tre secondi di voce.",
+ "known.caption": "Persone salvate in questo browser, con lo stato della loro {{noun}}",
+ "known.name": "Nome",
+ "known.added": "Aggiunto",
+ "known.actions": "Azioni",
+ "known.best": "corrispondenza migliore",
+ "known.notOnServer": "non sul server",
+ "known.notInBrowser": "non nell'elenco di questo browser",
+ "known.removing": "rimozione",
+ "known.goneText": "L'ultima ricerca non ha restituito questa persona. Il server potrebbe essere stato riavviato, quindi l'{{print}} è persa, oppure è stata registrata con un altro modello.",
+ "known.extraText": "Il server ha restituito questa persona, ma questo browser non ne ha traccia.",
+ "known.noLabels": "nessuna etichetta",
+ "known.undo": "Annulla",
+ "known.reenrolSaved": "Registra di nuovo dalla copia salvata",
+ "known.enrolAgain": "Registra di nuovo",
+ "known.play": "Riproduci il campione salvato di {{name}}",
+ "known.forget": "Rimuovi {{name}}",
+ "known.note": "Questo elenco è conservato in questo browser. Il server non ha una chiamata per elencare, quindi non si può verificare finché non esegui una ricerca.",
+ "known.noteSearched": "Questo elenco è conservato in questo browser. L'ultima ricerca lo ha confrontato con il server; una persona che non è stata restituita è segnalata.",
+ "enrol.close": "Chiudi",
+ "enrol.cancel": "Annulla",
+ "enrol.submit": "Registra",
+ "enrol.working": "Registrazione in corso",
+ "enrol.ready": "Pronto per registrare.",
+ "enrol.stillNeeded": "Manca ancora: {{list}}.",
+ "enrol.needModel": "un modello",
+ "enrol.needRecording": "una registrazione",
+ "enrol.needPhoto": "una foto",
+ "enrol.needName": "un nome",
+ "enrol.needTranscript": "una trascrizione",
+ "enrol.needPermission": "il permesso",
+ "enrol.recordingStep": "Registrazione",
+ "enrol.photoStep": "Foto",
+ "enrol.recordingLabel": "Registrazione",
+ "enrol.photoLabel": "Foto",
+ "enrol.whoStep": "Chi è",
+ "enrol.name": "Nome",
+ "enrol.namePlaceholder": "es. Marta Rossi",
+ "enrol.labels": "Etichette",
+ "enrol.labelsHint": "facoltative, una per riga come chiave: valore",
+ "enrol.asSpeech": "Conserva anche come voce di sintesi",
+ "enrol.asSpeechHint": "La sintesi vocale potrà poi parlare con questa voce. Serve la trascrizione esatta.",
+ "enrol.transcript": "Trascrizione esatta",
+ "enrol.permissionStep": "Permesso",
+ "enrol.agreed": "Questa persona ha acconsentito",
+ "enrol.keep": "Conserva una copia in questo browser",
+ "tts.title": "Sintesi vocale",
+ "tts.labels.model": "Modello",
+ "tts.labels.voice": "Voce",
+ "tts.labels.voicePlaceholder": "ID voce opzionale",
+ "tts.labels.input": "Testo",
+ "tts.labels.inputPlaceholder": "Inserisci il testo da convertire in voce...",
+ "tts.labels.instructions": "Istruzioni",
+ "tts.labels.instructionsPlaceholder": "Ad esempio: Parla lentamente e con calore",
+ "tts.labels.instructionsHint": "Indicazioni facoltative sullo stile. Il supporto dipende dal backend.",
+ "tts.actions.generate": "Genera audio",
+ "tts.actions.generating": "Generazione...",
+ "tts.empty": "L'audio generato apparirà qui",
+ "tts.toasts.noText": "Inserisci del testo",
+ "tts.toasts.noModel": "Seleziona un modello",
+ "tts.toasts.generated": "Voce generata",
+ "tts.toasts.generateFailed": "Generazione fallita",
+ "tts.voiceLibrary.cloningReady": "Clonazione vocale",
+ "tts.voiceLibrary.loading": "Caricamento delle personalità salvate…",
+ "tts.voiceLibrary.modelDefault": "Usa il predefinito del modello",
+ "tts.voiceLibrary.empty": "Nessuna personalità salvata finora.",
+ "tts.voiceLibrary.create": "Creane una",
+ "tts.voiceLibrary.manage": "Gestisci la libreria delle personalità",
+ "tts.voiceLibrary.namedVoiceHint": "Questo modello non accetta profili con audio di riferimento. Puoi comunque inserire un parlante con nome supportato dal suo backend.",
+ "voiceLibrary.title": "Voci di sintesi",
+ "voiceLibrary.subtitle": "Una registrazione e la sua trascrizione esatta. I modelli di sintesi vocale che accettano audio di riferimento possono parlare con essa.",
+ "voiceLibrary.loadFailed": "Non è stato possibile caricare le voci di sintesi",
+ "voiceLibrary.loading": "Caricamento delle personalità salvate…",
+ "voiceLibrary.listLabel": "Personalità vocali salvate",
+ "voiceLibrary.status.ready": "Pronto",
+ "voiceLibrary.summary.label": "Stato della libreria vocale",
+ "voiceLibrary.summary.profiles": "personalità salvate",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} modello compatibile pronto",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} modelli compatibili pronti",
+ "voiceLibrary.summary.noModels": "Nessun modello compatibile installato",
+ "voiceLibrary.search.label": "Cerca profili vocali",
+ "voiceLibrary.search.placeholder": "Cerca voci, lingue o trascrizioni",
+ "voiceLibrary.filters.language": "Filtra per lingua",
+ "voiceLibrary.filters.allLanguages": "Tutte le lingue",
+ "voiceLibrary.empty.title": "Ancora nessuna voce di sintesi",
+ "voiceLibrary.empty.body": "Registra o carica una clip di riferimento autorizzata, aggiungi la sua trascrizione esatta e riusa la voce con i modelli di sintesi vocale compatibili.",
+ "voiceLibrary.noResults.title": "Nessuna voce corrisponde a questi filtri",
+ "voiceLibrary.noResults.body": "Prova un'altra ricerca o mostra tutte le lingue.",
+ "voiceLibrary.detail.eyebrow": "Registrazione di riferimento e trascrizione esatta",
+ "voiceLibrary.detail.close": "Chiudi",
+ "voiceLibrary.detail.emptyTitle": "Seleziona una voce per esaminarla",
+ "voiceLibrary.detail.emptyBody": "Qui compariranno audio di riferimento, trascrizione, compatibilità e dettagli sul consenso.",
+ "voiceLibrary.detail.referenceAudio": "Audio di riferimento",
+ "voiceLibrary.detail.transcript": "Trascrizione di riferimento",
+ "voiceLibrary.metadata.language": "Lingua",
+ "voiceLibrary.metadata.languageUnknown": "Non specificata",
+ "voiceLibrary.metadata.duration": "Durata",
+ "voiceLibrary.metadata.sampleRate": "Frequenza di campionamento",
+ "voiceLibrary.metadata.created": "Creato",
+ "voiceLibrary.consent.label": "Permesso",
+ "voiceLibrary.consent.confirmed": "Consenso confermato",
+ "voiceLibrary.consent.confirmedAt": "Confermato il {{date}}",
+ "voiceLibrary.modelSetup.title": "Installa un modello di clonazione vocale",
+ "voiceLibrary.modelSetup.body": "Questa voce è pronta, ma serve un modello che accetti audio di riferimento. Scegline uno verificato da questo server.",
+ "voiceLibrary.modelSetup.loading": "Ricerca dei modelli compatibili nelle tue gallerie…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend scelto durante l'installazione",
+ "voiceLibrary.modelSetup.install": "Installa",
+ "voiceLibrary.modelSetup.installing": "Installazione…",
+ "voiceLibrary.modelSetup.loadFailed": "Non è stato possibile caricare i consigli compatibili.",
+ "voiceLibrary.modelSetup.noneAvailable": "Nelle gallerie configurate non c'è nessun modello compatibile da installare.",
+ "voiceLibrary.modelSetup.capabilityNote": "I consigli vengono dai metadati delle capacità dei modelli, non da un elenco di modelli nel browser.",
+ "voiceLibrary.api.title": "Uso dell'API e modelli compatibili",
+ "voiceLibrary.api.summary": "Usa questa voce fuori dal browser o verifica il supporto dei modelli",
+ "voiceLibrary.api.body": "Invia l'URI stabile della voce del profilo con ogni richiesta di sintesi. LocalAI risolve l'audio di riferimento privato e la trascrizione al momento della generazione; le impostazioni dei tuoi modelli non cambiano.",
+ "voiceLibrary.api.compatibleModels": "Modelli compatibili installati",
+ "voiceLibrary.api.noInstalledModels": "Ancora nessuno. Installa uno dei modelli verificati dal server qui sopra prima di inviare questa richiesta.",
+ "voiceLibrary.api.curlExample": "Esempio cURL",
+ "voiceLibrary.api.copy": "Copia la richiesta",
+ "voiceLibrary.api.endpointNote": "Gli stessi campi model, input e voice funzionano anche con POST /tts.",
+ "voiceLibrary.actions.create": "Aggiungi una voce di sintesi",
+ "voiceLibrary.actions.createFirst": "Aggiungi la tua prima voce di sintesi",
+ "voiceLibrary.actions.retry": "Riprova",
+ "voiceLibrary.actions.clearFilters": "Rimuovi filtri",
+ "voiceLibrary.actions.useInTTS": "Usa nella sintesi vocale",
+ "voiceLibrary.actions.delete": "Elimina la voce",
+ "voiceLibrary.actions.installModel": "Installa un modello compatibile",
+ "voiceLibrary.actions.browseModels": "Sfoglia tutti i modelli",
+ "voiceLibrary.deleteDialog.title": "Eliminare questa voce?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” e il suo audio di riferimento saranno rimossi in modo definitivo. I modelli configurati con questa voce non cambiano.",
+ "voiceLibrary.deleteDialog.deleting": "Eliminazione…",
+ "voiceLibrary.toasts.deleted": "{{name}} eliminata",
+ "voiceLibrary.toasts.installStarted": "Installazione di {{name}}. L'avanzamento è in Operazioni.",
+ "voiceLibrary.toasts.installFailed": "Non è stato possibile avviare l'installazione: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "Richiesta API copiata",
+ "voiceLibrary.toasts.copyFailed": "Non è stato possibile copiare la richiesta API",
+ "voiceCreate.title": "Aggiungi una voce di sintesi",
+ "voiceCreate.subtitle": "Una voce di sintesi è composta da una o più clip di riferimento pulite e dalle loro trascrizioni esatte. I modelli di sintesi vocale che accettano audio di riferimento possono parlare con essa.",
+ "voiceCreate.references.add": "Aggiungi un altro riferimento",
+ "voiceCreate.references.additional": "Riferimento {{number}}",
+ "voiceCreate.references.remove": "Rimuovi",
+ "voiceCreate.sections.reference.title": "Audio di riferimento",
+ "voiceCreate.sections.reference.body": "Registra nel browser o carica una clip esistente. La voce deve essere pulita, naturale e di un solo parlante.",
+ "voiceCreate.sections.details.title": "Identità e trascrizione",
+ "voiceCreate.sections.details.body": "Dai agli amministratori un nome riconoscibile e trascrivi la clip esattamente, con intercalari e punteggiatura.",
+ "voiceCreate.sections.consent.title": "Autorizzazione",
+ "voiceCreate.sections.consent.body": "La clonazione vocale può essere delicata. Conferma di avere il permesso prima di salvare questo riferimento.",
+ "voiceCreate.audio.label": "Riferimento vocale",
+ "voiceCreate.audio.normalizing": "Preparazione di un WAV PCM sicuro…",
+ "voiceCreate.audio.preview": "Riferimento normalizzato",
+ "voiceCreate.audio.tooLarge": "Questo file supera i 50 MiB.",
+ "voiceCreate.audio.durationError": "L'audio di riferimento deve durare tra 1 secondo e 2 minuti.",
+ "voiceCreate.audio.decodeError": "Questo browser non è riuscito a decodificare il file audio scelto.",
+ "voiceCreate.audio.qualityReady": "Durata consigliata",
+ "voiceCreate.audio.qualityHint": "Da 6 a 30 secondi di solito dà un clone più fedele",
+ "voiceCreate.fields.name": "Nome della voce",
+ "voiceCreate.fields.namePlaceholder": "es. Voce narrante di documentari",
+ "voiceCreate.fields.language": "Lingua (facoltativa)",
+ "voiceCreate.fields.languagePlaceholder": "es. it-IT",
+ "voiceCreate.fields.description": "Descrizione (facoltativa)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tono, origine o uso previsto",
+ "voiceCreate.fields.transcript": "Trascrizione esatta",
+ "voiceCreate.fields.transcriptPlaceholder": "Scrivi ogni parola pronunciata nella clip di riferimento…",
+ "voiceCreate.fields.transcriptHint": "Segui la registrazione alla lettera. Non correggere la grammatica e non togliere pause e intercalari.",
+ "voiceCreate.consent.title": "Confermo che questa voce può essere clonata",
+ "voiceCreate.consent.body": "Ho il permesso del parlante, o un'altra base legittima, per conservare e usare questa registrazione per la sintesi vocale.",
+ "voiceCreate.readiness.title": "Stato del profilo",
+ "voiceCreate.readiness.body": "Una voce diventa disponibile nella sintesi vocale quando ogni elemento richiesto è completo.",
+ "voiceCreate.readiness.audio": "Riferimento PCM-WAV valido",
+ "voiceCreate.readiness.quality": "Durata di qualità tra 6 e 30 secondi",
+ "voiceCreate.readiness.name": "Nome della voce riconoscibile",
+ "voiceCreate.readiness.transcript": "Trascrizione di riferimento esatta",
+ "voiceCreate.readiness.consent": "Autorizzazione confermata",
+ "voiceCreate.privacy.title": "Privato per impostazione predefinita. ",
+ "voiceCreate.privacy.body": "L'audio di riferimento è conservato nella cartella dati di LocalAI con permessi di file ristretti e non è mai esposto come percorso nella cartella dei modelli.",
+ "voiceCreate.actions.back": "Torna alla libreria",
+ "voiceCreate.actions.cancel": "Annulla",
+ "voiceCreate.actions.save": "Salva la voce",
+ "voiceCreate.actions.saving": "Salvataggio…",
+ "voiceCreate.toasts.created": "{{name}} è pronta all'uso",
+ "clip.mic": "microfono",
+ "clip.camera": "fotocamera",
+ "clip.record": "Registra",
+ "clip.takePhoto": "Scatta una foto",
+ "clip.chooseAudio": "Scegli un file",
+ "clip.choosePhoto": "Scegli una foto",
+ "clip.readAloud": "Leggi a voce alta, a ritmo normale",
+ "clip.remove": "Rimuovi",
+ "clip.blocked": "L'accesso a «{{thing}}» è bloccato. Consentilo nelle impostazioni del sito del browser e riprova, oppure scegli un file.",
+ "clip.insecure": "Il browser apre «{{thing}}» solo su https o su localhost. Ora sei su {{origin}}. Apri questa pagina da localhost o aggiungi TLS, oppure scegli un file.",
+ "clip.unsupported": "Questo browser non può aprire «{{thing}}». Scegli invece un file.",
+ "clip.missing": "Su questo dispositivo non è stato trovato «{{thing}}». Scegli invece un file.",
+ "clip.failed": "Non è stato possibile aprire «{{thing}}». Scegli invece un file.",
+ "clip.allowed": "Microfono consentito.",
+ "clip.capture": "Acquisisci",
+ "clip.cancel": "Annulla",
+ "clip.recording": "Registrazione in corso, {{s}} s",
+ "clip.micReady": "Microfono pronto",
+ "clip.stop": "Ferma",
+ "clip.start": "Avvia la registrazione",
+ "clip.recorded": "Registrazione",
+ "clip.photographed": "Foto",
+ "clip.uploaded": "File caricato",
+ "clip.goodLength": "durata adatta",
+ "clip.short": "breve",
+ "clip.checkLevel": "controlla il livello",
+ "clip.preview": "Anteprima",
+ "clip.photoAlt": "La foto che hai scelto",
+ "same.text": "Distanza tra le due impronte ({{print}}): {{distance}}. Sotto {{cutoff}} si considera la stessa persona.",
+ "same.distance": "distanza",
+ "word.strong": "Corrispondenza forte",
+ "word.likely": "Corrispondenza probabile",
+ "word.close": "Caso al limite",
+ "word.near": "Appena oltre la soglia",
+ "scale.aria": "Scala delle distanze con la soglia a {{cutoff}}. Più basso significa più simile. {{dots}}",
+ "scale.cutoff": "soglia {{value}}",
+ "scale.axis": "distanza tra impronte ({{print}}), più bassa significa più simili",
+ "scale.oneDot": "un punto per ogni registrazione ({{person}})",
+ "scale.onePair": "un punto per la coppia",
+ "ranks.aria": "Persone più vicine, dalla più vicina",
+ "ranks.within": "entro la soglia",
+ "ranks.far": "troppo lontana",
+ "how.title": "Come leggere questo risultato.",
+ "how.body": "La distanza dice quanto sono lontane due impronte ({{print}}); 0 significa identiche. La soglia è la linea tra stessa persona e persone diverse. La parola Forte o Probabile dice quanto un risultato sta dentro la linea. Non è una probabilità.",
+ "stored.title": "Cosa viene conservato e dove",
+ "toast.enrolled": "{{name}} registrato",
+ "toast.enrolledBoth": "{{name}} registrato e voce di sintesi salvata",
+ "toast.tooBig": "La copia era troppo grande per la memoria del browser, quindi non è stata conservata.",
+ "toast.speechFailed": "Registrazione riuscita, ma la voce di sintesi non è stata salvata: {{message}}",
+ "toast.removing": "Rimozione di {{name}}…",
+ "toast.removed": "{{name}} rimosso",
+ "toast.alreadyGone": "{{name}} non c'era già più sul server.",
+ "error.retry": "Riprova",
+ "error.traces": "Apri le tracce",
+ "needed.badge": "Non attivo su questo server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Installa",
+ "needed.installing": "Installazione",
+ "needed.started": "Installazione di {{name}}. L'avanzamento è in Operazioni.",
+ "needed.loadFailed": "Non è stato possibile elencare i modelli.",
+ "needed.noneListed": "Nelle gallerie non c'è nessun modello corrispondente.",
+ "needed.browse": "Sfoglia tutti i modelli",
+ "needed.askAdmin": "Chiedi a un amministratore di installarne uno.",
+ "needed.whatTitle": "Cosa lo attiva",
+ "off.badge": "Non attivo per il tuo account",
+ "off.voice.title": "Il riconoscimento vocale è disattivato per il tuo account",
+ "off.voice.text": "Un amministratore non ti ha consentito di usare il riconoscimento vocale.",
+ "off.voice.what": "Un amministratore può attivare il permesso di riconoscimento vocale per il tuo utente in Utenti e chiavi.",
+ "recording.title": "Dai un nome ai parlanti di una registrazione",
+ "recording.text": "L'area di lavoro di diarizzazione trova chi ha parlato e quando in una registrazione. Da lì puoi ricordare un parlante, e quella voce entra nell'elenco dei parlanti qui.",
+ "recording.step1": "Apri l'area di lavoro, aggiungi una registrazione e spunta Prepara i parlanti da ricordare.",
+ "recording.step2": "Avvia la diarizzazione. Ogni parlante con abbastanza voce pulita ottiene un pulsante Dai un nome e ricorda.",
+ "recording.step3": "Dai un nome a un parlante e ricordalo. Quella voce compare poi in Parlanti, pronta per Chi è.",
+ "recording.open": "Apri l'area di lavoro di diarizzazione",
+ "recording.off": "La diarizzazione non è attiva per il tuo account.",
+ "recording.warning": "Le voci ricordate sono condivise su questo server e si perdono al riavvio. Non viene ricordato nulla in automatico."
+}
diff --git a/addons/voice/ui/src/i18n/voice.ko.json b/addons/voice/ui/src/i18n/voice.ko.json
new file mode 100644
index 0000000000..18df3e581a
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.ko.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voice",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "텍스트",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "결과",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "음성 ID (선택 사항)",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "음성",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "인식",
+ "voices.tab.library": "라이브러리",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "진행 중",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "이름",
+ "known.added": "추가됨",
+ "known.actions": "작업",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "실행 취소",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "{{name}} 제거",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "닫기",
+ "enrol.cancel": "취소",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "이름",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "텍스트 음성 변환",
+ "tts.labels.model": "모델",
+ "tts.labels.voice": "음성",
+ "tts.labels.voicePlaceholder": "음성 ID (선택 사항)",
+ "tts.labels.input": "텍스트",
+ "tts.labels.inputPlaceholder": "합성할 텍스트를 입력하세요...",
+ "tts.labels.instructions": "지침",
+ "tts.labels.instructionsPlaceholder": "예: 천천히 따뜻한 어조로 말하기",
+ "tts.labels.instructionsHint": "선택 사항인 말하기 지침입니다. 지원 여부는 백엔드에 따라 다릅니다.",
+ "tts.actions.generate": "생성",
+ "tts.actions.generating": "생성 중...",
+ "tts.empty": "생성된 오디오가 여기에 표시됩니다",
+ "tts.toasts.noText": "텍스트를 입력해 주세요",
+ "tts.toasts.noModel": "모델을 선택해 주세요",
+ "tts.toasts.generated": "Speech generated",
+ "tts.toasts.generateFailed": "생성에 실패했습니다",
+ "tts.voiceLibrary.cloningReady": "Voice cloning",
+ "tts.voiceLibrary.loading": "Loading saved personalities…",
+ "tts.voiceLibrary.modelDefault": "Use model default",
+ "tts.voiceLibrary.empty": "No saved personalities yet.",
+ "tts.voiceLibrary.create": "Create one",
+ "tts.voiceLibrary.manage": "Manage Personality Library",
+ "tts.voiceLibrary.namedVoiceHint": "This model does not accept reference-audio profiles. You can still enter a named speaker supported by its backend.",
+ "voiceLibrary.title": "Speech voices",
+ "voiceLibrary.subtitle": "A recording and its exact transcript. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Loading saved personalities…",
+ "voiceLibrary.listLabel": "Saved voice personalities",
+ "voiceLibrary.status.ready": "준비됨",
+ "voiceLibrary.summary.label": "Voice library status",
+ "voiceLibrary.summary.profiles": "saved personalities",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} compatible model ready",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} compatible models ready",
+ "voiceLibrary.summary.noModels": "No compatible model installed",
+ "voiceLibrary.search.label": "Search voice profiles",
+ "voiceLibrary.search.placeholder": "Search voices, languages, or transcripts",
+ "voiceLibrary.filters.language": "Filter by language",
+ "voiceLibrary.filters.allLanguages": "All languages",
+ "voiceLibrary.empty.title": "No speech voices yet",
+ "voiceLibrary.empty.body": "Record or upload a consented reference clip, add its exact transcript, and reuse the voice across compatible text-to-speech models.",
+ "voiceLibrary.noResults.title": "No voices match these filters",
+ "voiceLibrary.noResults.body": "Try a different search or show every language.",
+ "voiceLibrary.detail.eyebrow": "Reference recording and exact transcript",
+ "voiceLibrary.detail.close": "닫기",
+ "voiceLibrary.detail.emptyTitle": "Select a voice to inspect it",
+ "voiceLibrary.detail.emptyBody": "Reference audio, transcript, compatibility, and consent details will appear here.",
+ "voiceLibrary.detail.referenceAudio": "Reference audio",
+ "voiceLibrary.detail.transcript": "Reference transcript",
+ "voiceLibrary.metadata.language": "언어",
+ "voiceLibrary.metadata.languageUnknown": "Not specified",
+ "voiceLibrary.metadata.duration": "Duration",
+ "voiceLibrary.metadata.sampleRate": "Sample rate",
+ "voiceLibrary.metadata.created": "만듦",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consent confirmed",
+ "voiceLibrary.consent.confirmedAt": "Confirmed {{date}}",
+ "voiceLibrary.modelSetup.title": "Install a voice-cloning model",
+ "voiceLibrary.modelSetup.body": "This voice is ready, but it needs a model that accepts reference audio. Choose one verified by this server.",
+ "voiceLibrary.modelSetup.loading": "Finding compatible models in your galleries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selected during install",
+ "voiceLibrary.modelSetup.install": "설치",
+ "voiceLibrary.modelSetup.installing": "설치 중…",
+ "voiceLibrary.modelSetup.loadFailed": "Compatible recommendations could not be loaded.",
+ "voiceLibrary.modelSetup.noneAvailable": "No compatible uninstalled model is available in the configured galleries.",
+ "voiceLibrary.modelSetup.capabilityNote": "Recommendations come from model capability metadata, not a browser-side model list.",
+ "voiceLibrary.api.title": "API usage and compatible models",
+ "voiceLibrary.api.summary": "Use this voice outside the browser or check model support",
+ "voiceLibrary.api.body": "Send the profile’s stable voice URI with each speech request. LocalAI resolves the private reference audio and transcript at generation time; the settings of your models are not changed.",
+ "voiceLibrary.api.compatibleModels": "Compatible installed models",
+ "voiceLibrary.api.noInstalledModels": "None yet. Install one of the server-verified models above before sending this request.",
+ "voiceLibrary.api.curlExample": "cURL example",
+ "voiceLibrary.api.copy": "Copy request",
+ "voiceLibrary.api.endpointNote": "The same model, input, and voice fields also work with POST /tts.",
+ "voiceLibrary.actions.create": "Add a speech voice",
+ "voiceLibrary.actions.createFirst": "Add your first speech voice",
+ "voiceLibrary.actions.retry": "다시 시도",
+ "voiceLibrary.actions.clearFilters": "필터 지우기",
+ "voiceLibrary.actions.useInTTS": "Use in Text to Speech",
+ "voiceLibrary.actions.delete": "Delete voice",
+ "voiceLibrary.actions.installModel": "Install a compatible model",
+ "voiceLibrary.actions.browseModels": "Browse all models",
+ "voiceLibrary.deleteDialog.title": "Delete this voice?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” and its reference audio will be permanently removed. Models set up with this voice are not changed.",
+ "voiceLibrary.deleteDialog.deleting": "Deleting…",
+ "voiceLibrary.toasts.deleted": "Deleted {{name}}",
+ "voiceLibrary.toasts.installStarted": "Installing {{name}}. Progress is available in Operations.",
+ "voiceLibrary.toasts.installFailed": "Could not start installation: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "API request copied",
+ "voiceLibrary.toasts.copyFailed": "Could not copy the API request",
+ "voiceCreate.title": "Add a speech voice",
+ "voiceCreate.subtitle": "A speech voice is one or more clean reference clips and their exact transcripts. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "제거",
+ "voiceCreate.sections.reference.title": "Reference audio",
+ "voiceCreate.sections.reference.body": "Record in the browser or upload an existing clip. Speech should be clean, natural, and from one speaker.",
+ "voiceCreate.sections.details.title": "Identity and transcript",
+ "voiceCreate.sections.details.body": "Give admins a recognizable name and transcribe the clip exactly, including fillers and punctuation.",
+ "voiceCreate.sections.consent.title": "Authorization",
+ "voiceCreate.sections.consent.body": "Voice cloning can be sensitive. Confirm permission before this reference is saved.",
+ "voiceCreate.audio.label": "Voice reference",
+ "voiceCreate.audio.normalizing": "Preparing a secure PCM WAV…",
+ "voiceCreate.audio.preview": "Normalized reference",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "Reference audio must be between 1 second and 2 minutes.",
+ "voiceCreate.audio.decodeError": "This browser could not decode the selected audio file.",
+ "voiceCreate.audio.qualityReady": "Recommended length",
+ "voiceCreate.audio.qualityHint": "6–30 seconds usually produces a stronger clone",
+ "voiceCreate.fields.name": "Voice name",
+ "voiceCreate.fields.namePlaceholder": "e.g. Documentary narrator",
+ "voiceCreate.fields.language": "Language (optional)",
+ "voiceCreate.fields.languagePlaceholder": "e.g. en-US",
+ "voiceCreate.fields.description": "Description (optional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tone, source, or intended use",
+ "voiceCreate.fields.transcript": "Exact transcript",
+ "voiceCreate.fields.transcriptPlaceholder": "Type every word spoken in the reference clip…",
+ "voiceCreate.fields.transcriptHint": "Match the recording exactly. Do not rewrite grammar or remove pauses and fillers.",
+ "voiceCreate.consent.title": "I confirm that this voice may be cloned",
+ "voiceCreate.consent.body": "I have the speaker’s permission or another lawful basis to store and use this recording for synthesized speech.",
+ "voiceCreate.readiness.title": "Profile readiness",
+ "voiceCreate.readiness.body": "A voice becomes available in Text to Speech when every required item is complete.",
+ "voiceCreate.readiness.audio": "Valid PCM-WAV reference",
+ "voiceCreate.readiness.quality": "6–30 second quality window",
+ "voiceCreate.readiness.name": "Recognizable voice name",
+ "voiceCreate.readiness.transcript": "Exact reference transcript",
+ "voiceCreate.readiness.consent": "Authorization confirmed",
+ "voiceCreate.privacy.title": "Private by default. ",
+ "voiceCreate.privacy.body": "Reference audio is stored in LocalAI’s data directory with restricted file permissions and is never exposed as a model-folder path.",
+ "voiceCreate.actions.back": "Back to library",
+ "voiceCreate.actions.cancel": "취소",
+ "voiceCreate.actions.save": "Save voice",
+ "voiceCreate.actions.saving": "Saving…",
+ "voiceCreate.toasts.created": "{{name}} is ready to use",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "제거",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "취소",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "중지",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "미리보기",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "다시 시도",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "설치",
+ "needed.installing": "설치 중",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/voice.pt-BR.json b/addons/voice/ui/src/i18n/voice.pt-BR.json
new file mode 100644
index 0000000000..fc5aa1338d
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.pt-BR.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voz",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "Texto",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "Resultado",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "Falante opcional ou ID de voz",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "Vozes",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "Reconhecimento",
+ "voices.tab.library": "Biblioteca",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "Em andamento",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "Nome",
+ "known.added": "Adicionado",
+ "known.actions": "Ações",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "Desfazer",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "Remover {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "Fechar",
+ "enrol.cancel": "Cancelar",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "Nome",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Transcrição exata",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "Texto para Fala",
+ "tts.labels.model": "Modelo",
+ "tts.labels.voice": "Voz",
+ "tts.labels.voicePlaceholder": "Falante opcional ou ID de voz",
+ "tts.labels.input": "Texto",
+ "tts.labels.inputPlaceholder": "Digite o texto para sintetizar...",
+ "tts.labels.instructions": "Instruções",
+ "tts.labels.instructionsPlaceholder": "Por exemplo: Fale devagar e com calor",
+ "tts.labels.instructionsHint": "Orientações opcionais de interpretação. O suporte depende do backend.",
+ "tts.actions.generate": "Gerar",
+ "tts.actions.generating": "Gerando...",
+ "tts.empty": "O áudio gerado aparecerá aqui",
+ "tts.toasts.noText": "Digite o texto",
+ "tts.toasts.noModel": "Selecione um modelo",
+ "tts.toasts.generated": "Fala gerada",
+ "tts.toasts.generateFailed": "Falha na geração",
+ "tts.voiceLibrary.cloningReady": "Clonagem de voz",
+ "tts.voiceLibrary.loading": "Carregando vozes salvas…",
+ "tts.voiceLibrary.modelDefault": "Usar padrão do modelo",
+ "tts.voiceLibrary.empty": "Nenhuma voz salva ainda.",
+ "tts.voiceLibrary.create": "Criar uma",
+ "tts.voiceLibrary.manage": "Gerenciar Biblioteca de Vozes",
+ "tts.voiceLibrary.namedVoiceHint": "Este modelo não aceita perfis de áudio de referência. Você ainda pode digitar um falante nomeado suportado pelo seu backend.",
+ "voiceLibrary.title": "Biblioteca de Vozes",
+ "voiceLibrary.subtitle": "Crie e gerencie vozes de referência reutilizáveis para cada modelo instalado que suporta clonagem de voz.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Carregando vozes salvas…",
+ "voiceLibrary.listLabel": "Perfis de voz salvos",
+ "voiceLibrary.status.ready": "Pronto",
+ "voiceLibrary.summary.label": "Status da biblioteca de vozes",
+ "voiceLibrary.summary.profiles": "vozes salvas",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} modelo compatível pronto",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} modelos compatíveis prontos",
+ "voiceLibrary.summary.noModels": "Nenhum modelo compatível instalado",
+ "voiceLibrary.search.label": "Buscar perfis de voz",
+ "voiceLibrary.search.placeholder": "Buscar vozes, idiomas ou transcrições",
+ "voiceLibrary.filters.language": "Filtrar por idioma",
+ "voiceLibrary.filters.allLanguages": "Todos os idiomas",
+ "voiceLibrary.empty.title": "Sua biblioteca de vozes está vazia",
+ "voiceLibrary.empty.body": "Grave ou envie um clipe de referência autorizado, adicione a transcrição exata e reuse-o nos modelos de TTS compatíveis.",
+ "voiceLibrary.noResults.title": "Nenhuma voz corresponde a estes filtros",
+ "voiceLibrary.noResults.body": "Tente uma busca diferente ou mostre todos os idiomas.",
+ "voiceLibrary.detail.eyebrow": "Voz selecionada",
+ "voiceLibrary.detail.close": "Fechar",
+ "voiceLibrary.detail.emptyTitle": "Selecione uma voz para inspecioná-la",
+ "voiceLibrary.detail.emptyBody": "Áudio de referência, transcrição, compatibilidade e detalhes de consentimento aparecerão aqui.",
+ "voiceLibrary.detail.referenceAudio": "Áudio de referência",
+ "voiceLibrary.detail.transcript": "Transcrição de referência",
+ "voiceLibrary.metadata.language": "Idioma",
+ "voiceLibrary.metadata.languageUnknown": "Não especificado",
+ "voiceLibrary.metadata.duration": "Duração",
+ "voiceLibrary.metadata.sampleRate": "Taxa de amostragem",
+ "voiceLibrary.metadata.created": "Criado em",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consentimento confirmado",
+ "voiceLibrary.consent.confirmedAt": "Confirmado em {{date}}",
+ "voiceLibrary.modelSetup.title": "Instalar um modelo de clonagem de voz",
+ "voiceLibrary.modelSetup.body": "Esta voz está pronta, mas precisa de um modelo que aceite áudio de referência. Escolha um verificado por este servidor.",
+ "voiceLibrary.modelSetup.loading": "Encontrando modelos compatíveis em suas galerias…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selecionado durante a instalação",
+ "voiceLibrary.modelSetup.install": "Instalar",
+ "voiceLibrary.modelSetup.installing": "Instalando…",
+ "voiceLibrary.modelSetup.loadFailed": "Recomendações compatíveis não puderam ser carregadas.",
+ "voiceLibrary.modelSetup.noneAvailable": "Nenhum modelo compatível não instalado está disponível nas galerias configuradas.",
+ "voiceLibrary.modelSetup.capabilityNote": "As recomendações vêm dos metadados de capacidade do modelo, não de uma lista de modelos do lado do navegador.",
+ "voiceLibrary.api.title": "Uso da API e modelos compatíveis",
+ "voiceLibrary.api.summary": "Use esta voz fora do navegador ou verifique o suporte do modelo",
+ "voiceLibrary.api.body": "Envie a URI de voz estável do perfil com cada requisição de fala. O LocalAI resolve o áudio de referência privado e a transcrição no momento da geração; as configurações dos seus modelos não são alteradas.",
+ "voiceLibrary.api.compatibleModels": "Modelos compatíveis instalados",
+ "voiceLibrary.api.noInstalledModels": "Nenhum ainda. Instale um dos modelos verificados pelo servidor acima antes de enviar esta requisição.",
+ "voiceLibrary.api.curlExample": "Exemplo com cURL",
+ "voiceLibrary.api.copy": "Copiar requisição",
+ "voiceLibrary.api.endpointNote": "Os mesmos campos de modelo, entrada e voz também funcionam com POST /tts.",
+ "voiceLibrary.actions.create": "Criar voz",
+ "voiceLibrary.actions.createFirst": "Crie sua primeira voz",
+ "voiceLibrary.actions.retry": "Tentar novamente",
+ "voiceLibrary.actions.clearFilters": "Limpar filtros",
+ "voiceLibrary.actions.useInTTS": "Usar em Texto para Fala",
+ "voiceLibrary.actions.delete": "Excluir voz",
+ "voiceLibrary.actions.installModel": "Instalar um modelo compatível",
+ "voiceLibrary.actions.browseModels": "Explorar todos os modelos",
+ "voiceLibrary.deleteDialog.title": "Excluir esta voz?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” e seu áudio de referência serão removidos permanentemente. Os modelos configurados com esta voz não são alterados.",
+ "voiceLibrary.deleteDialog.deleting": "Excluindo…",
+ "voiceLibrary.toasts.deleted": "Voz {{name}} excluída",
+ "voiceLibrary.toasts.installStarted": "Instalando {{name}}. O progresso está disponível em Operações.",
+ "voiceLibrary.toasts.installFailed": "Não foi possível iniciar a instalação: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "Requisição de API copiada",
+ "voiceLibrary.toasts.copyFailed": "Não foi possível copiar a requisição de API",
+ "voiceCreate.title": "Criar uma voz reutilizável",
+ "voiceCreate.subtitle": "Adicione um clipe de referência limpo e sua transcrição exata. O LocalAI cuida dos parâmetros de clonagem específicos do backend no momento da geração.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "Remover",
+ "voiceCreate.sections.reference.title": "Áudio de referência",
+ "voiceCreate.sections.reference.body": "Grave no navegador ou envie um clipe existente. A fala deve ser limpa, natural e de um único falante.",
+ "voiceCreate.sections.details.title": "Identidade e transcrição",
+ "voiceCreate.sections.details.body": "Dê aos administradores um nome reconhecível e transcreva o clipe exatamente, incluindo hesitações e pontuação.",
+ "voiceCreate.sections.consent.title": "Autorização",
+ "voiceCreate.sections.consent.body": "A clonagem de voz pode ser sensível. Confirme a permissão antes que esta referência seja salva.",
+ "voiceCreate.audio.label": "Referência de voz",
+ "voiceCreate.audio.normalizing": "Preparando um PCM WAV seguro…",
+ "voiceCreate.audio.preview": "Referência normalizada",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "O áudio de referência deve ter entre 1 segundo e 2 minutos.",
+ "voiceCreate.audio.decodeError": "Este navegador não conseguiu decodificar o arquivo de áudio selecionado.",
+ "voiceCreate.audio.qualityReady": "Duração recomendada",
+ "voiceCreate.audio.qualityHint": "6–30 segundos geralmente produzem um clone mais forte",
+ "voiceCreate.fields.name": "Nome da voz",
+ "voiceCreate.fields.namePlaceholder": "ex.: Narrador de documentário",
+ "voiceCreate.fields.language": "Idioma (opcional)",
+ "voiceCreate.fields.languagePlaceholder": "ex.: pt-BR",
+ "voiceCreate.fields.description": "Descrição (opcional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tom, origem ou uso pretendido",
+ "voiceCreate.fields.transcript": "Transcrição exata",
+ "voiceCreate.fields.transcriptPlaceholder": "Digite cada palavra falada no clipe de referência…",
+ "voiceCreate.fields.transcriptHint": "Corresponda à gravação exatamente. Não reescreva a gramática nem remova pausas e hesitações.",
+ "voiceCreate.consent.title": "Confirmo que esta voz pode ser clonada",
+ "voiceCreate.consent.body": "Tenho a permissão do falante ou outra base legal para armazenar e usar esta gravação para fala sintetizada.",
+ "voiceCreate.readiness.title": "Prontidão do perfil",
+ "voiceCreate.readiness.body": "Uma voz fica disponível em Texto para Fala quando todos os itens obrigatórios estão completos.",
+ "voiceCreate.readiness.audio": "Referência PCM-WAV válida",
+ "voiceCreate.readiness.quality": "Janela de qualidade de 6–30 segundos",
+ "voiceCreate.readiness.name": "Nome de voz reconhecível",
+ "voiceCreate.readiness.transcript": "Transcrição de referência exata",
+ "voiceCreate.readiness.consent": "Autorização confirmada",
+ "voiceCreate.privacy.title": "Privada por padrão. ",
+ "voiceCreate.privacy.body": "O áudio de referência é armazenado no diretório de dados do LocalAI com permissões de arquivo restritas e nunca é exposto como um caminho de pasta de modelo.",
+ "voiceCreate.actions.back": "Voltar para a biblioteca",
+ "voiceCreate.actions.cancel": "Cancelar",
+ "voiceCreate.actions.save": "Salvar voz",
+ "voiceCreate.actions.saving": "Salvando…",
+ "voiceCreate.toasts.created": "{{name}} está pronta para uso",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Gravar",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "Remover",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "Cancelar",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "Parar",
+ "clip.start": "Iniciar gravação",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "Pré-visualização",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "Tentar novamente",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "Instalar",
+ "needed.installing": "Instalando",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Explorar todos os modelos",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/src/i18n/voice.zh-CN.json b/addons/voice/ui/src/i18n/voice.zh-CN.json
new file mode 100644
index 0000000000..5ad6a95a7a
--- /dev/null
+++ b/addons/voice/ui/src/i18n/voice.zh-CN.json
@@ -0,0 +1,341 @@
+{
+ "voice.adminOnly.title": "Administrators only",
+ "voice.adminOnly.text": "Saved voices can be managed by an administrator of this server. Ask one if you need a voice added or removed.",
+ "speech.title": "Speak text",
+ "speech.lede": "Turn text into speech with a voice model of this server.",
+ "speech.model": "Model",
+ "speech.voice": "Voice",
+ "speech.defaultVoice": "Default voice",
+ "speech.text": "文本",
+ "speech.instructions": "Instructions (optional)",
+ "speech.generate": "Generate speech",
+ "speech.result": "结果",
+ "speech.noModels": "No text-to-speech model is installed.",
+ "speech.noModelsLink": "Get one from the model gallery.",
+ "speech.voicePlaceholder": "可选的声音 ID",
+ "speech.textPlaceholder": "What should it say?",
+ "speech.noAccess": "Your account has no access to text-to-speech. The server will refuse the request.",
+ "voices.title": "声音",
+ "voices.lede": "Recognise who is speaking, and keep voices that text-to-speech can use.",
+ "voices.tabs": "Voices sections",
+ "voices.tab.recognition": "识别",
+ "voices.tab.library": "库",
+ "voices.tab.speech": "Text to speech",
+ "voices.tab.recording": "From a recording",
+ "tool.aria": "Recognition tool",
+ "tool.sameTab": "Same person?",
+ "tool.clip": "Clip",
+ "tool.clipA": "Clip A",
+ "tool.clipB": "Clip B",
+ "tool.photo": "Photo",
+ "tool.photoA": "Photo A",
+ "tool.photoB": "Photo B",
+ "tool.compare": "Compare",
+ "tool.comparing": "Comparing",
+ "tool.workingAria": "进行中",
+ "voice.speaker": "speaker",
+ "voice.print": "voiceprint",
+ "voice.whoTab": "Who is this",
+ "voice.whoLede": "Matches a clip against the people you enrolled.",
+ "voice.sameLede": "Two clips, one question. Nobody has to be enrolled.",
+ "voice.whoGo": "Identify speaker",
+ "voice.working": "Identifying",
+ "voice.whoNote": "Searches the speakers enrolled on this server. The clip you test with is not stored.",
+ "voice.sameNote": "The answer comes from one voiceprint per clip. Nothing is stored.",
+ "voice.workingText": "Comparing the clip with the voiceprints the server holds.",
+ "voice.probably": "Probably {{name}}",
+ "voice.matchText": "{{name}}'s voiceprint is the closest of {{count}} compared, {{distance}} away. Anything under {{cutoff}} counts as the same person.",
+ "voice.nobody": "Nobody enrolled sounds like this",
+ "voice.missText": "The closest was {{name}} at {{distance}}, past the cut-off of {{cutoff}}. That usually means a new speaker, or a clip that is too short or noisy.",
+ "voice.noneEnrolled": "Nobody is enrolled to compare with",
+ "voice.noneEnrolledText": "Enrol a speaker first, then identify a clip against them.",
+ "voice.noneReturned": "The server returned no speakers",
+ "voice.noneReturnedText": "It holds no voiceprints made with this model. If the server restarted, enrolled voices are gone: enrol them again.",
+ "voice.enrolThis": "Enrol this voice",
+ "voice.same": "Probably the same person",
+ "voice.different": "Probably different people",
+ "voice.knownTitle": "Known speakers",
+ "voice.enrol": "Enrol a speaker",
+ "voice.enrolTitle": "Enrol a speaker",
+ "voice.enrolIntro": "The server keeps a voiceprint and a name. It does not keep the recording.",
+ "voice.emptyTitle": "Nobody enrolled in this browser yet",
+ "voice.emptyText": "Enrol a speaker with a short recording, then test a clip against them.",
+ "voice.agreedHint": "I have their permission, or another lawful basis, to store a voiceprint of them.",
+ "voice.keepHint": "Lets you replay it and enrol again after a server restart. Off by default. It is stored in this browser only.",
+ "voice.neededTitle": "Voices need a speaker model",
+ "voice.neededText": "Voice recognition turns a recording into a voiceprint and compares voiceprints. It runs on a speaker-recognition model, and none is installed on this server.",
+ "voice.neededWhat": "Install a model that supports speaker recognition. Once one is installed, this page turns on by itself. Users also need the Voice recognition permission.",
+ "voice.moreTitle": "More tools",
+ "voice.moreHint": "guess a speaker's traits, get the raw voiceprint",
+ "voice.why.0": "One speaker is assumed. A clip with two people mixes both voices.",
+ "voice.why.1": "A different microphone or room changes how a voice sounds.",
+ "voice.why.2": "This is a similarity score, not a security check. Do not use it to unlock anything.",
+ "voice.stored.0.where": "On the server",
+ "voice.stored.0.what": "A {{print}} (numbers that describe the voice, not the audio), a name and your labels. They are kept in memory, are shared by everyone who uses this server, and are lost when it restarts. The recording is not kept.",
+ "voice.stored.1.where": "In this browser",
+ "voice.stored.1.what": "The name, labels and time of each person you enrolled. A copy of the recording only if you tick Keep a copy when enrolling.",
+ "voice.stored.2.where": "Clips you test with",
+ "voice.stored.2.what": "They go to the model on this server to be compared and are not kept.",
+ "cutoff.label": "Cut-off",
+ "cutoff.default": "model default",
+ "cutoff.reset": "Reset to {{value}}",
+ "cutoff.stricter": "Stricter",
+ "cutoff.looser": "Looser",
+ "cutoff.explain": "Stricter means fewer wrong matches and more misses. The answer above updates as you move it. The server is not asked again.",
+ "why.title": "Why this might be wrong",
+ "why.short": "The clip is short: {{s}} s. Three seconds or more of speech works better.",
+ "known.caption": "People saved in this browser, with their {{noun}} state",
+ "known.name": "名称",
+ "known.added": "添加时间",
+ "known.actions": "操作",
+ "known.best": "best match",
+ "known.notOnServer": "not on the server",
+ "known.notInBrowser": "not in this browser's list",
+ "known.removing": "removing",
+ "known.goneText": "The last search did not return this person. The server may have restarted, so the {{print}} is gone, or it was enrolled with another model.",
+ "known.extraText": "The server returned this person but this browser has no record of them.",
+ "known.noLabels": "no labels",
+ "known.undo": "撤销",
+ "known.reenrolSaved": "Re-enrol from saved copy",
+ "known.enrolAgain": "Enrol again",
+ "known.play": "Play the saved sample of {{name}}",
+ "known.forget": "移除 {{name}}",
+ "known.note": "This list is kept in this browser. The server has no list call, so it cannot be checked until you run a search.",
+ "known.noteSearched": "This list is kept in this browser. The last search checked it against the server; a person it did not return is marked.",
+ "enrol.close": "关闭",
+ "enrol.cancel": "取消",
+ "enrol.submit": "Enrol",
+ "enrol.working": "Enrolling",
+ "enrol.ready": "Ready to enrol.",
+ "enrol.stillNeeded": "Still needed: {{list}}.",
+ "enrol.needModel": "a model",
+ "enrol.needRecording": "a recording",
+ "enrol.needPhoto": "a photo",
+ "enrol.needName": "a name",
+ "enrol.needTranscript": "a transcript",
+ "enrol.needPermission": "permission",
+ "enrol.recordingStep": "Recording",
+ "enrol.photoStep": "Photo",
+ "enrol.recordingLabel": "Recording",
+ "enrol.photoLabel": "Photo",
+ "enrol.whoStep": "Who is it",
+ "enrol.name": "名称",
+ "enrol.namePlaceholder": "e.g. Marta Rossi",
+ "enrol.labels": "Labels",
+ "enrol.labelsHint": "optional, one per line as key: value",
+ "enrol.asSpeech": "Also keep it as a speech voice",
+ "enrol.asSpeechHint": "Text-to-speech can then speak in this voice. Needs the exact transcript.",
+ "enrol.transcript": "Exact transcript",
+ "enrol.permissionStep": "Permission",
+ "enrol.agreed": "This person agreed",
+ "enrol.keep": "Keep a copy in this browser",
+ "tts.title": "文字转语音",
+ "tts.labels.model": "模型",
+ "tts.labels.voice": "声音",
+ "tts.labels.voicePlaceholder": "可选的声音 ID",
+ "tts.labels.input": "文本",
+ "tts.labels.inputPlaceholder": "输入要合成的文本...",
+ "tts.labels.instructions": "朗读指令",
+ "tts.labels.instructionsPlaceholder": "例如:缓慢、温暖地朗读",
+ "tts.labels.instructionsHint": "可选的表达方式指导。是否支持取决于后端。",
+ "tts.actions.generate": "生成音频",
+ "tts.actions.generating": "生成中...",
+ "tts.empty": "生成的音频将显示在此处",
+ "tts.toasts.noText": "请输入文本",
+ "tts.toasts.noModel": "请选择一个模型",
+ "tts.toasts.generated": "Speech generated",
+ "tts.toasts.generateFailed": "生成失败",
+ "tts.voiceLibrary.cloningReady": "Voice cloning",
+ "tts.voiceLibrary.loading": "Loading saved personalities…",
+ "tts.voiceLibrary.modelDefault": "Use model default",
+ "tts.voiceLibrary.empty": "No saved personalities yet.",
+ "tts.voiceLibrary.create": "Create one",
+ "tts.voiceLibrary.manage": "Manage Personality Library",
+ "tts.voiceLibrary.namedVoiceHint": "This model does not accept reference-audio profiles. You can still enter a named speaker supported by its backend.",
+ "voiceLibrary.title": "Speech voices",
+ "voiceLibrary.subtitle": "A recording and its exact transcript. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceLibrary.loadFailed": "Speech voices could not be loaded",
+ "voiceLibrary.loading": "Loading saved personalities…",
+ "voiceLibrary.listLabel": "Saved voice personalities",
+ "voiceLibrary.status.ready": "已就绪",
+ "voiceLibrary.summary.label": "Voice library status",
+ "voiceLibrary.summary.profiles": "saved personalities",
+ "voiceLibrary.summary.modelsReady_one": "{{count}} compatible model ready",
+ "voiceLibrary.summary.modelsReady_other": "{{count}} compatible models ready",
+ "voiceLibrary.summary.noModels": "No compatible model installed",
+ "voiceLibrary.search.label": "Search voice profiles",
+ "voiceLibrary.search.placeholder": "Search voices, languages, or transcripts",
+ "voiceLibrary.filters.language": "Filter by language",
+ "voiceLibrary.filters.allLanguages": "All languages",
+ "voiceLibrary.empty.title": "No speech voices yet",
+ "voiceLibrary.empty.body": "Record or upload a consented reference clip, add its exact transcript, and reuse the voice across compatible text-to-speech models.",
+ "voiceLibrary.noResults.title": "No voices match these filters",
+ "voiceLibrary.noResults.body": "Try a different search or show every language.",
+ "voiceLibrary.detail.eyebrow": "Reference recording and exact transcript",
+ "voiceLibrary.detail.close": "关闭",
+ "voiceLibrary.detail.emptyTitle": "Select a voice to inspect it",
+ "voiceLibrary.detail.emptyBody": "Reference audio, transcript, compatibility, and consent details will appear here.",
+ "voiceLibrary.detail.referenceAudio": "Reference audio",
+ "voiceLibrary.detail.transcript": "Reference transcript",
+ "voiceLibrary.metadata.language": "语言",
+ "voiceLibrary.metadata.languageUnknown": "Not specified",
+ "voiceLibrary.metadata.duration": "Duration",
+ "voiceLibrary.metadata.sampleRate": "Sample rate",
+ "voiceLibrary.metadata.created": "已创建",
+ "voiceLibrary.consent.label": "Permission",
+ "voiceLibrary.consent.confirmed": "Consent confirmed",
+ "voiceLibrary.consent.confirmedAt": "Confirmed {{date}}",
+ "voiceLibrary.modelSetup.title": "Install a voice-cloning model",
+ "voiceLibrary.modelSetup.body": "This voice is ready, but it needs a model that accepts reference audio. Choose one verified by this server.",
+ "voiceLibrary.modelSetup.loading": "Finding compatible models in your galleries…",
+ "voiceLibrary.modelSetup.backendUnknown": "Backend selected during install",
+ "voiceLibrary.modelSetup.install": "安装",
+ "voiceLibrary.modelSetup.installing": "正在安装…",
+ "voiceLibrary.modelSetup.loadFailed": "Compatible recommendations could not be loaded.",
+ "voiceLibrary.modelSetup.noneAvailable": "No compatible uninstalled model is available in the configured galleries.",
+ "voiceLibrary.modelSetup.capabilityNote": "Recommendations come from model capability metadata, not a browser-side model list.",
+ "voiceLibrary.api.title": "API usage and compatible models",
+ "voiceLibrary.api.summary": "Use this voice outside the browser or check model support",
+ "voiceLibrary.api.body": "Send the profile’s stable voice URI with each speech request. LocalAI resolves the private reference audio and transcript at generation time; the settings of your models are not changed.",
+ "voiceLibrary.api.compatibleModels": "Compatible installed models",
+ "voiceLibrary.api.noInstalledModels": "None yet. Install one of the server-verified models above before sending this request.",
+ "voiceLibrary.api.curlExample": "cURL example",
+ "voiceLibrary.api.copy": "Copy request",
+ "voiceLibrary.api.endpointNote": "The same model, input, and voice fields also work with POST /tts.",
+ "voiceLibrary.actions.create": "Add a speech voice",
+ "voiceLibrary.actions.createFirst": "Add your first speech voice",
+ "voiceLibrary.actions.retry": "重试",
+ "voiceLibrary.actions.clearFilters": "清除筛选",
+ "voiceLibrary.actions.useInTTS": "Use in Text to Speech",
+ "voiceLibrary.actions.delete": "Delete voice",
+ "voiceLibrary.actions.installModel": "Install a compatible model",
+ "voiceLibrary.actions.browseModels": "Browse all models",
+ "voiceLibrary.deleteDialog.title": "Delete this voice?",
+ "voiceLibrary.deleteDialog.message": "“{{name}}” and its reference audio will be permanently removed. Models set up with this voice are not changed.",
+ "voiceLibrary.deleteDialog.deleting": "Deleting…",
+ "voiceLibrary.toasts.deleted": "Deleted {{name}}",
+ "voiceLibrary.toasts.installStarted": "Installing {{name}}. Progress is available in Operations.",
+ "voiceLibrary.toasts.installFailed": "Could not start installation: {{message}}",
+ "voiceLibrary.toasts.apiCopied": "API request copied",
+ "voiceLibrary.toasts.copyFailed": "Could not copy the API request",
+ "voiceCreate.title": "Add a speech voice",
+ "voiceCreate.subtitle": "A speech voice is one or more clean reference clips and their exact transcripts. Text-to-speech models that accept reference audio can speak with it.",
+ "voiceCreate.references.add": "Add another reference",
+ "voiceCreate.references.additional": "Reference {{number}}",
+ "voiceCreate.references.remove": "移除",
+ "voiceCreate.sections.reference.title": "Reference audio",
+ "voiceCreate.sections.reference.body": "Record in the browser or upload an existing clip. Speech should be clean, natural, and from one speaker.",
+ "voiceCreate.sections.details.title": "Identity and transcript",
+ "voiceCreate.sections.details.body": "Give admins a recognizable name and transcribe the clip exactly, including fillers and punctuation.",
+ "voiceCreate.sections.consent.title": "Authorization",
+ "voiceCreate.sections.consent.body": "Voice cloning can be sensitive. Confirm permission before this reference is saved.",
+ "voiceCreate.audio.label": "Voice reference",
+ "voiceCreate.audio.normalizing": "Preparing a secure PCM WAV…",
+ "voiceCreate.audio.preview": "Normalized reference",
+ "voiceCreate.audio.tooLarge": "This file is larger than 50 MiB.",
+ "voiceCreate.audio.durationError": "Reference audio must be between 1 second and 2 minutes.",
+ "voiceCreate.audio.decodeError": "This browser could not decode the selected audio file.",
+ "voiceCreate.audio.qualityReady": "Recommended length",
+ "voiceCreate.audio.qualityHint": "6–30 seconds usually produces a stronger clone",
+ "voiceCreate.fields.name": "Voice name",
+ "voiceCreate.fields.namePlaceholder": "e.g. Documentary narrator",
+ "voiceCreate.fields.language": "Language (optional)",
+ "voiceCreate.fields.languagePlaceholder": "e.g. en-US",
+ "voiceCreate.fields.description": "Description (optional)",
+ "voiceCreate.fields.descriptionPlaceholder": "Tone, source, or intended use",
+ "voiceCreate.fields.transcript": "Exact transcript",
+ "voiceCreate.fields.transcriptPlaceholder": "Type every word spoken in the reference clip…",
+ "voiceCreate.fields.transcriptHint": "Match the recording exactly. Do not rewrite grammar or remove pauses and fillers.",
+ "voiceCreate.consent.title": "I confirm that this voice may be cloned",
+ "voiceCreate.consent.body": "I have the speaker’s permission or another lawful basis to store and use this recording for synthesized speech.",
+ "voiceCreate.readiness.title": "Profile readiness",
+ "voiceCreate.readiness.body": "A voice becomes available in Text to Speech when every required item is complete.",
+ "voiceCreate.readiness.audio": "Valid PCM-WAV reference",
+ "voiceCreate.readiness.quality": "6–30 second quality window",
+ "voiceCreate.readiness.name": "Recognizable voice name",
+ "voiceCreate.readiness.transcript": "Exact reference transcript",
+ "voiceCreate.readiness.consent": "Authorization confirmed",
+ "voiceCreate.privacy.title": "Private by default. ",
+ "voiceCreate.privacy.body": "Reference audio is stored in LocalAI’s data directory with restricted file permissions and is never exposed as a model-folder path.",
+ "voiceCreate.actions.back": "Back to library",
+ "voiceCreate.actions.cancel": "取消",
+ "voiceCreate.actions.save": "Save voice",
+ "voiceCreate.actions.saving": "Saving…",
+ "voiceCreate.toasts.created": "{{name}} is ready to use",
+ "clip.mic": "microphone",
+ "clip.camera": "camera",
+ "clip.record": "Record",
+ "clip.takePhoto": "Take a photo",
+ "clip.chooseAudio": "Choose a file",
+ "clip.choosePhoto": "Choose a photo",
+ "clip.readAloud": "Read this aloud, normal pace",
+ "clip.remove": "移除",
+ "clip.blocked": "The {{thing}} is blocked. Allow it in the browser's site settings and try again, or choose a file instead.",
+ "clip.insecure": "A browser only opens the {{thing}} on https or on localhost. You are on {{origin}}. Open this page through localhost or add TLS, or choose a file.",
+ "clip.unsupported": "This browser cannot open the {{thing}}. Choose a file instead.",
+ "clip.missing": "No {{thing}} was found on this device. Choose a file instead.",
+ "clip.failed": "The {{thing}} could not be opened. Choose a file instead.",
+ "clip.allowed": "Microphone allowed.",
+ "clip.capture": "Capture",
+ "clip.cancel": "取消",
+ "clip.recording": "Recording, {{s}} s",
+ "clip.micReady": "Microphone ready",
+ "clip.stop": "停止",
+ "clip.start": "Start recording",
+ "clip.recorded": "Recording",
+ "clip.photographed": "Photo",
+ "clip.uploaded": "Uploaded file",
+ "clip.goodLength": "good length",
+ "clip.short": "short",
+ "clip.checkLevel": "check level",
+ "clip.preview": "预览",
+ "clip.photoAlt": "The photo you chose",
+ "same.text": "The two {{print}}s are {{distance}} apart. Anything under {{cutoff}} counts as the same person.",
+ "same.distance": "distance",
+ "word.strong": "Strong match",
+ "word.likely": "Likely match",
+ "word.close": "Close call",
+ "word.near": "Just past the cut-off",
+ "scale.aria": "Distance scale with the cut-off at {{cutoff}}. Lower means more alike. {{dots}}",
+ "scale.cutoff": "cut-off {{value}}",
+ "scale.axis": "distance between {{print}}s, lower means more alike",
+ "scale.oneDot": "one dot per enrolled {{person}}",
+ "scale.onePair": "one dot for the pair",
+ "ranks.aria": "Closest people, nearest first",
+ "ranks.within": "within cut-off",
+ "ranks.far": "too far",
+ "how.title": "How to read this.",
+ "how.body": "Distance is how far apart two {{print}}s are, 0 meaning identical. The cut-off is the line between same and different. The word Strong or Likely says how far inside the line a result sits. It is not a probability.",
+ "stored.title": "What is stored, and where",
+ "toast.enrolled": "Enrolled {{name}}",
+ "toast.enrolledBoth": "Enrolled {{name}} and saved a speech voice",
+ "toast.tooBig": "The copy was too large for browser storage, so it was not kept.",
+ "toast.speechFailed": "Enrolled, but the speech voice could not be saved: {{message}}",
+ "toast.removing": "{{name}} will be removed.",
+ "toast.removed": "Removed {{name}}",
+ "toast.alreadyGone": "{{name}} was already gone from the server.",
+ "error.retry": "重试",
+ "error.traces": "Open traces",
+ "needed.badge": "Not enabled on this server",
+ "needed.backend": "backend {{backend}}",
+ "needed.install": "安装",
+ "needed.installing": "正在安装",
+ "needed.started": "Installing {{name}}. Progress is in Operations.",
+ "needed.loadFailed": "Models could not be listed.",
+ "needed.noneListed": "No matching model is listed in the galleries.",
+ "needed.browse": "Browse all models",
+ "needed.askAdmin": "Ask an administrator to install one.",
+ "needed.whatTitle": "What turns this on",
+ "off.badge": "Not enabled for your account",
+ "off.voice.title": "Voice recognition is off for your account",
+ "off.voice.text": "An administrator has not allowed you to use voice recognition.",
+ "off.voice.what": "An administrator can turn on the Voice recognition permission for your user in Users and keys.",
+ "recording.title": "Name the speakers in a recording",
+ "recording.text": "The diarization workspace finds who spoke when in a recording. From there you can remember a speaker, and that voice joins the Speakers list here.",
+ "recording.step1": "Open the workspace, add a recording and tick Prepare speakers to remember.",
+ "recording.step2": "Run diarization. Each speaker with enough clean speech gets a Name and remember button.",
+ "recording.step3": "Name a speaker and remember it. That voice then appears under Speakers, ready for Who is this.",
+ "recording.open": "Open the diarization workspace",
+ "recording.off": "Diarization is not turned on for your account.",
+ "recording.warning": "Remembered voices are shared on this server and are lost when it restarts. Nothing is remembered automatically."
+}
diff --git a/addons/voice/ui/test/render.test.mjs b/addons/voice/ui/test/render.test.mjs
index b3e386ace5..ec8ed8aa1d 100644
--- a/addons/voice/ui/test/render.test.mjs
+++ b/addons/voice/ui/test/render.test.mjs
@@ -94,3 +94,11 @@ test('render: each tab draws exactly one h1, the one Voice title (the add form k
assert.deepEqual(h1s(renderToString(React.createElement(addon.Component, { page: 'voice', path: 'library' }))), ['Voices'])
assert.deepEqual(h1s(renderToString(React.createElement(addon.Component, { page: 'voice', path: 'library/new' }))), ['Add a speech voice'])
})
+
+test('render: the page reads in the language of the web UI, with English for a key it lacks', async () => {
+ const it = await load({ language: () => 'it' })
+ const addon = await it.mod.createAddon(it.sdk)
+ const html = renderToString(React.createElement(addon.Component, { page: 'voice', path: 'recognition' }))
+ assert.match(html, /Riconoscimento/)
+ assert.doesNotMatch(html, />Recognition)
+})
diff --git a/core/http/react-ui/e2e/memory-addon-notice.spec.js b/core/http/react-ui/e2e/memory-addon-notice.spec.js
index e99a3023ab..6b5a0953cf 100644
--- a/core/http/react-ui/e2e/memory-addon-notice.spec.js
+++ b/core/http/react-ui/e2e/memory-addon-notice.spec.js
@@ -14,7 +14,7 @@ test.beforeAll(async () => {
await buildAddonBundles(['memory'])
})
-test('the add-on page has a notice that memory is built in, with a link to Settings > Memory', async ({ page }) => {
+async function serveMemory(page) {
await page.route('**/api/auth/status', route => route.fulfill({ json: sessionFor(ADMIN, { addons: true }) }))
await stubAddonRoutes(page)
await page.route(/\/api\/addons\/memory\/assets\//, (route) => {
@@ -25,6 +25,10 @@ test('the add-on page has a notice that memory is built in, with a link to Setti
})
await page.route('**/api/addons/memory/user-settings', route => route.fulfill({ json: { settings: [{ key: 'save-mode', values: ['ask', 'auto', 'never'], default: 'ask', value: 'ask' }] } }))
await mockAddons(page, { apps: [packagedApp('memory')] })
+}
+
+test('the add-on page has a notice that memory is built in, with a link to Settings > Memory', async ({ page }) => {
+ await serveMemory(page)
await page.goto('/app/addons/memory/memory')
const notice = page.getByTestId('memory-builtin-notice')
await expect(notice).toBeVisible()
@@ -34,3 +38,14 @@ test('the add-on page has a notice that memory is built in, with a link to Setti
await notice.getByRole('button', { name: 'Open Settings > Memory' }).click()
await expect(page).toHaveURL(/\/app\/account\?tab=memory$/)
})
+
+test('the add-on page reads in the language of the web UI', async ({ page }) => {
+ await page.addInitScript(() => localStorage.setItem('localai-language', 'it'))
+ await serveMemory(page)
+ await page.goto('/app/addons/memory/memory')
+ await expect(page.getByTestId('memory-page')).toContainText('Memoria breve')
+ await expect(page.getByTestId('memory-builtin-notice')).toContainText('La memoria è integrata')
+ await expect(page.getByTestId('memory-save')).toContainText('Salvare una nota')
+ await expect(page.getByTestId('memory-open')).toHaveText('Apri le mie note')
+ await expect(page.getByTestId('memory-page')).not.toContainText('Short memory')
+})
diff --git a/docs/content/features/addons.md b/docs/content/features/addons.md
index 1f3e32589a..95c37e51f7 100644
--- a/docs/content/features/addons.md
+++ b/docs/content/features/addons.md
@@ -5144,6 +5144,12 @@ when a language lacks a bundle, a bundle lacks a key of English or has an extra
from English, or a value is empty. There is no baseline: add a key to English and every language in the
same change.
+The Voice, Face, Fine-tuning, Quantization and Memory pages read their words from the same kind of bundle
+(`addons//ui/src/i18n/..json`, flat keys). A list of notes is stored as numbered keys
+(`voice.why.0`, `voice.stored.0.where`) and the page puts it back together, so each item is translated like any
+other text. A string that has no translation yet repeats the English text in that language's bundle, which
+satisfies the gate and shows English; replace it when a translation exists.
+
Compile an add-on with classic JSX transformed to `React.createElement` over
`sdk.React`, inside the `createAddon` factory. Do not import a JSX runtime. The fixture
packages under `tests/addons/fixture/ui` are written in that form.