mirror of
https://github.com/penpot/penpot.git
synced 2026-09-23 04:16:13 +00:00
🌐 Multi-locale PO checker with word catalogs
Split the checker engine from its word lists: ca/es catalogs now live in scripts/check-translations/words.<locale>.txt and all messages are in English. Adds an es seed (calibrated to zero errors) and fixes 7 typos it found in es.po. Universal checks (placeholders, plurals, punctuation) run without a catalog. AI-assisted-by: muse-spark-1.3-contributor
This commit is contained in:
parent
a9ec397368
commit
cffce049e3
@ -58,25 +58,22 @@ high-coverage support reference, never the base.
|
|||||||
## QA before commit
|
## QA before commit
|
||||||
|
|
||||||
- Run `node ./scripts/check-translations.js -l <locale>` from
|
- Run `node ./scripts/check-translations.js -l <locale>` from
|
||||||
`frontend/` (also as `pnpm run check-translations` for `ca`):
|
`frontend/` (`pnpm run check-translations` covers `ca`): 0 errors
|
||||||
0 errors required; review warnings by hand. New valid words that
|
required; review warnings by hand. Word lists live in
|
||||||
trip the gate go to `PARAULES_OK` in the script; `--self-test`
|
`frontend/scripts/check-translations/words.<locale>.txt`
|
||||||
covers the detector rules.
|
(`[elision]` `[function]` `[common]` `[ok]` `[brands]`); new valid
|
||||||
|
words that trip the gate go to `[ok]`; `--self-test` covers the
|
||||||
|
detector rules. Without a catalog only the universal checks run.
|
||||||
|
`#, fuzzy` entries are skipped (known-pending, owned elsewhere).
|
||||||
- Placeholder parity per entry (singular AND each plural form,
|
- Placeholder parity per entry (singular AND each plural form,
|
||||||
also enforced by the script); verify `%s` against the `tr` call
|
also enforced by the script); verify `%s` against the `tr` call
|
||||||
site when `en`/`es`/code disagree (a `%s` the code never passes
|
site when `en`/`es`/code disagree (a `%s` the code never passes
|
||||||
renders literally; a dropped one swallows the argument).
|
renders literally; a dropped one swallows the argument).
|
||||||
- Glued words (AI batches drop spaces at wrap boundaries): tokenize
|
- Glued words (AI batches drop spaces at wrap boundaries): the
|
||||||
`msgstr` against a Catalan frequency list and review every
|
script flags function-word splits (`del'equip`, `lapolítica`,
|
||||||
out-of-vocabulary token splittable as function-word + word
|
`sinecessiteu`), `,/.`/`:` without following space,
|
||||||
(`del'equip`, `lapolítica`, `sinecessiteu`), plus `,/.`/`:` without
|
lowercase+Uppercase joins (`delPenpot`, `oCapitalize`) and `%s`
|
||||||
following space, lowercase+Uppercase joins (`delPenpot`,
|
glued to a word.
|
||||||
`oCapitalize`), `%s` glued to a word, and entries whose `ca` word
|
|
||||||
count is far below `en`. Known-valid splits, do NOT touch:
|
|
||||||
`compartides`, `edita`, `emplenament`, `desant`, `niar`, `negreta`,
|
|
||||||
`selector`, `atributs`, `sobreescriuran`, adverbs in `-ment`,
|
|
||||||
futures/participles (`desbloquegeu`, `predeterminat`,
|
|
||||||
`seleccionades`, `descarregueu`).
|
|
||||||
- Balanced `[]`/`()` in markdown links; no double spaces; no glued
|
- Balanced `[]`/`()` in markdown links; no double spaces; no glued
|
||||||
words around `·`; trailing spaces match the source.
|
words around `·`; trailing spaces match the source.
|
||||||
- `git diff --stat` must touch only `frontend/translations/<locale>.po`.
|
- `git diff --stat` must touch only `frontend/translations/<locale>.po`.
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// QA per a fitxers PO de traduccions: detecta paraules enganxades per
|
// QA for PO translation files: finds words glued by a missing space,
|
||||||
// falta d'espai, placeholders trencats i estructures de plural perdudes.
|
// broken placeholders and lost plural structures.
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage (from `frontend/`, like `translations.js`):
|
||||||
// node ./scripts/check-translations.js [-l <locale>] [--self-test]
|
// node ./scripts/check-translations.js [-l <locale>] [--self-test]
|
||||||
//
|
//
|
||||||
// Exit: 0 sense errors (els avisos no fallen), 1 amb errors, 2 mal us.
|
// Exit: 0 with no errors (warnings don't fail), 1 with errors,
|
||||||
// Cal executar-ho des de `frontend/` (com `translations.js`).
|
// 2 on misuse or unreadable files.
|
||||||
|
//
|
||||||
|
// Language data lives in `./scripts/check-translations/words.<locale>.txt`
|
||||||
|
// (sections: [elision] [function] [common] [ok] [brands]). Without a
|
||||||
|
// catalog only the language-independent checks run (placeholders,
|
||||||
|
// plurals, punctuation, camelCase).
|
||||||
|
|
||||||
import getopts from "getopts";
|
import getopts from "getopts";
|
||||||
import { promises as fs } from "node:fs";
|
import { promises as fs } from "node:fs";
|
||||||
import gt from "gettext-parser";
|
import gt from "gettext-parser";
|
||||||
|
|
||||||
// Paraules funcionals curtes: si obren un token desconegut, gairebe
|
// Brands kept as-is in every locale.
|
||||||
// segur que falta un espai (`del'equip`, `lapolitica`, `sinecessiteu`).
|
const GENERIC_BRANDS = [
|
||||||
const FUNCIO = new Set(
|
|
||||||
"i o a e de del dels la el els les lo un una uns unes al als en amb per pel pels que com no ni si ja se es ho hi li me te ne em et us vos ens son són és més mes tot tota molt tan tant on quan perquè pero però doncs fins entre sobre sota cap cada altre seva seu meva teu nostre vostre aquest aquesta això allò jo tu ell ella nosaltres vosaltres ells".split(
|
|
||||||
" ",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Marques que legitimen una unio minuscula+Majuscula.
|
|
||||||
const MARQUES_OK = [
|
|
||||||
"GitHub",
|
"GitHub",
|
||||||
"GitLab",
|
"GitLab",
|
||||||
"YouTube",
|
"YouTube",
|
||||||
@ -32,112 +29,96 @@ const MARQUES_OK = [
|
|||||||
"macOS",
|
"macOS",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Vocabulari comu (verbs, noms, adjectius frequents) per a validar
|
|
||||||
// la part no-funcional d'una possible particio. Sense dependencia
|
|
||||||
// externa: llista curada a ma. Si falta una paraula, el cas caura a
|
|
||||||
// `avis` en comptes de `error`, mai en silenci.
|
|
||||||
const PARAULES_COMUNES = new Set(
|
|
||||||
"corregir poder obtenir mantindran targeta controladors importants futura donant tingui informar diversitat continuï juntament revocaran habilitar desament autenticació family aplicació còpies configurat necessita conflictes trigar res directament enviï correccions admet exportacions aplicacions càrrec envia enllaç actuals ajuda moment valor equips conjunt projecte fitxer compte usuari persona persones cosa temps part text nom contrasenya sessió idioma versió canvi arxiu error avís filtre cerca vista pestanya botó camp llista taula imatge forma capa fons color mida data correu propietari propietària membre opcions prova suprimireu interactuïn ajudarem quedi configurat permetre continueu breu manteniment present començar donant css font prioritzar suborganitzacions podreu poden facturació".split(
|
|
||||||
" ",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Paraules correctes que el divisor parteix en dues parts valides
|
|
||||||
// (`segura` -> `segur` + `a`). Revisades a ma; si el checker es queixa
|
|
||||||
// d'una paraula correcta nova, afegiu-la aqui.
|
|
||||||
const PARAULES_OK = new Set(
|
|
||||||
"ajudarem aplana coincideixi coma comes comprova comprovar comprovar-ho comuna convidarem estarem existeixen existeixi fase gratuïta interna mateixa meves molta oberta obertes permeten permeti segura targeta teus usa usen niar negreta emplena emplenament edita selector atributs sobreescriuran compartides desenvolupadors administradors desbloquejar desbloquegeu opcionalment autoreferència especificant previsualització previsualitza predeterminat predeterminats predeterminada predefinides predefinida desactivades seleccionada seleccionades descarregueu centralitzat horitzontalment verticalment multijugador multiorganització complementari multiplicador actualitzarà activades activador cancel·laran comptaran configurar confirmar-ho conservaran desagrupa desbloqueja desenganxa despublica despublicar desselecciona-ho duplicades envia-ho importada importades interactius intercanvia lliscament lliuraran migracions mixte personalitzeu-les plantilles privadesa realitzant repositori restauraran selecciona-ho superposa transferiu unir-se usar-los autodesat demanar-ho desactivar-los abandonar-la accedir-hi accelereu activar-los actualitzeu adoptarà afegir-hi afegir-ne afegiu afegiu-ne agrupant-la ajudar-nos ajudar-vos ajudeu-nos ajudieu-nos ajustar-lo ajusteu ajustis alineació amplia ampliada amplieu animacions aplicant apliqueu arrossegueu assegurar-vos avançar-vos avisos baixades cancel·lada cancel·lant cercador coincidents col·laboració col·laborar commuteu compatibilitat concedir-hi consumeixin conèixer-vos definir-ne definiu depuració desbloca descarta-ho descriviu desemmascara deshabilita deshabilitada deshabilitades desplaçar desvincula dissenyeu editar-lo editen editeu eliminar-lo el·lipse el·lipses emplenats encabir-ho enfosqueix escriviu-nos espaiat espaiats especifiqueu estils expliqueu-nos exporta exportant exportar flexibilitat gaudireu il·limitat il·limitats il·lumina il·lustracions importats incrusta inhabilitada inicieu insereix inspecciona inspeccionar-ne instal·la instal·lació instal·lada instal·lades instal·lat instantànies integracions intentar-ho marca-ho migració milloreu milloreu-ho notificacions obsolet obteniu ometeu-ho omplir-ho opacitat orientar-vos personalitzada personalitzades pestanya porta-ho porta-retalls promoveu prototipar prototipatge publicar-la reassignar reassigneu remapejant remapeja remapejar silenciats sol·licitat sol·licitud sol·licituds suprimiran tipogràfic tipogràfica tipogràfics tipogràfiques torna-ho valorant vinculades vincular visiteu volteja unir-s'hi l'opacitat l'espaiat l'interlineat explora'n edita'l desant aquesta aquestes baixa capa capes compartir comproveu desa deseu dreta esteu files fixa inicia inicials meus només nous noves pel perfil perquè pes pla seus totes treballo una vosaltres vàlida vàlides desactiva desactivat desactivada desconnecta descobreix connectades alguna canvia compost comprovant del dels des enviarem mixtes noms quina senzilla uneix ves vista vés desconegut fixa pel pels tota comentari comunitat set desvinculat separades".split(
|
|
||||||
" ",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const TOKEN_RE = /[\p{L}\p{M}]+(?:[·'’\-][\p{L}\p{M}]+)*/gu;
|
const TOKEN_RE = /[\p{L}\p{M}]+(?:[·'’\-][\p{L}\p{M}]+)*/gu;
|
||||||
const SKIP_RE = /[%{@/\\=<>|#0-9]/;
|
const SKIP_RE = /[%{@/\\=<>|#0-9]/;
|
||||||
const PLACEHOLDER_RES = [/%[sd]/g, /\{[^}]*\}/g, /%\([^)]*\)[sd]/g];
|
const PLACEHOLDER_RES = [/%[sd]/g, /\{[^}]*\}/g, /%\([^)]*\)[sd]/g];
|
||||||
const PUNT_RE = /[,.:;!?…»)\]]([A-Za-zÀ-Úà-ú«("“‘$])/gu;
|
const PUNCT_RE = /[,.:;!?…»)\]]([A-Za-zÀ-Úà-ú«("“‘$])/gu;
|
||||||
const CAMEL_RE = /[a-zàèéíòóúüç·]([A-ZÀÈÉÍÒÓÚÜ][a-zàèéíòóúü]+)/gu;
|
const CAMEL_RE = /[a-zàèéíòóúüç·]([A-ZÀÈÉÍÒÓÚÜ][a-zàèéíòóúü]+)/gu;
|
||||||
const PH_GLUED_RE = /%[sd](?=[A-Za-zÀ-Úà-ú])/gu;
|
const PH_GLUED_RE = /%[sd](?=[A-Za-zÀ-Úà-ú])/gu;
|
||||||
|
const APOSTROPHE_DIGIT_RE = /[a-zàèéíòóúüç·]d['’][0-9]/gu;
|
||||||
|
|
||||||
function tokenitza(text) {
|
function tokenize(text) {
|
||||||
return [...text.matchAll(TOKEN_RE)].map((m) => m[0].toLowerCase());
|
return [...text.matchAll(TOKEN_RE)].map((m) => m[0].toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function baseApostrof(token) {
|
function elisionBase(token, elision) {
|
||||||
const m = token.match(/^[ldsmntc]['’](.+)$/);
|
if (!elision) return null;
|
||||||
|
const m = token.match(new RegExp(`^[${elision}]['’](.+)$`));
|
||||||
return m ? m[1] : null;
|
return m ? m[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function compte(text, re) {
|
function countIn(text, re) {
|
||||||
re.lastIndex = 0;
|
re.lastIndex = 0;
|
||||||
return [...text.matchAll(re)].length;
|
return [...text.matchAll(re)].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validesa d'una PART d'una particio (mai la paraula sencera: un
|
// A word is valid as one half of a split. Never the whole token:
|
||||||
// enganxat repetit no s'ha d'auto-validar perque surt molt).
|
// a repeated glue (`del'equip` x3) must not validate itself by frequency.
|
||||||
function partValida(pal, freq) {
|
function validPart(word, words, freq) {
|
||||||
return (
|
return (
|
||||||
FUNCIO.has(pal) ||
|
words.functionWords.has(word) ||
|
||||||
PARAULES_OK.has(pal) ||
|
words.okWords.has(word) ||
|
||||||
PARAULES_COMUNES.has(pal) ||
|
words.commonWords.has(word) ||
|
||||||
(freq.get(pal) ?? 0) >= 1
|
(freq.get(word) ?? 0) >= 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Totes les particions (esq, dre, regla) d'un token, incloent-hi
|
// Every (left, right, rule) split of a token, including each
|
||||||
// cada segment separat per guionet.
|
// hyphen-separated segment.
|
||||||
function* particions(tok, freq) {
|
function* splits(tok, words, freq) {
|
||||||
const cands = [tok, ...tok.split("-")];
|
const cands = [tok, ...tok.split("-")];
|
||||||
const vistos = new Set();
|
const seen = new Set();
|
||||||
for (const c of cands) {
|
for (const c of cands) {
|
||||||
if (c.length < 3) continue;
|
if (c.length < 3) continue;
|
||||||
for (let k = 1; k < c.length; k++) {
|
for (let k = 1; k < c.length; k++) {
|
||||||
const esq = c.slice(0, k);
|
const left = c.slice(0, k);
|
||||||
const dre = c.slice(k);
|
const right = c.slice(k);
|
||||||
if (esq.length < 1) continue;
|
if (left.length < 1) continue;
|
||||||
if (dre.length < 2 && !FUNCIO.has(dre)) continue;
|
if (right.length < 2 && !words.functionWords.has(right)) continue;
|
||||||
const clau = esq + "|" + dre;
|
const key = left + "|" + right;
|
||||||
if (vistos.has(clau)) continue;
|
if (seen.has(key)) continue;
|
||||||
vistos.add(clau);
|
seen.add(key);
|
||||||
const dreBase = baseApostrof(dre) ?? dre;
|
const rightBase = elisionBase(right, words.elision) ?? right;
|
||||||
const dreOk = partValida(dreBase, freq);
|
const rightOk = validPart(rightBase, words, freq);
|
||||||
const esqOk = partValida(esq, freq);
|
const leftOk = validPart(left, words, freq);
|
||||||
if (FUNCIO.has(esq) && dreOk) yield [esq, dre, "func-esq"];
|
if (words.functionWords.has(left) && rightOk)
|
||||||
else if (FUNCIO.has(dre) && dre.length <= 4 && esqOk)
|
yield [left, right, "func-left"];
|
||||||
yield [esq, dre, "func-dre"];
|
else if (words.functionWords.has(right) && right.length <= 4 && leftOk)
|
||||||
|
yield [left, right, "func-right"];
|
||||||
else if (
|
else if (
|
||||||
c.length >= 8 &&
|
c.length >= 8 &&
|
||||||
esq.length >= 3 &&
|
left.length >= 3 &&
|
||||||
dre.length >= 2 &&
|
right.length >= 2 &&
|
||||||
esqOk &&
|
leftOk &&
|
||||||
dreOk
|
rightOk
|
||||||
)
|
)
|
||||||
yield [esq, dre, "contingut"];
|
yield [left, right, "content"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ORDRE = { "func-esq": 0, "func-dre": 1, contingut: 2 };
|
const RULE_ORDER = { "func-left": 0, "func-right": 1, content: 2 };
|
||||||
|
|
||||||
function millorParticio(tok, freq) {
|
function bestSplit(tok, words, freq) {
|
||||||
let millor = null;
|
let best = null;
|
||||||
for (const [esq, dre, regla] of particions(tok, freq)) {
|
for (const [left, right, rule] of splits(tok, words, freq)) {
|
||||||
if (esq.includes("-")) continue;
|
if (left.includes("-")) continue;
|
||||||
if (
|
if (
|
||||||
!millor ||
|
!best ||
|
||||||
ORDRE[regla] < ORDRE[millor[2]] ||
|
RULE_ORDER[rule] < RULE_ORDER[best[2]] ||
|
||||||
(ORDRE[regla] === ORDRE[millor[2]] && esq.length > millor[0].length)
|
(RULE_ORDER[rule] === RULE_ORDER[best[2]] && left.length > best[0].length)
|
||||||
) {
|
) {
|
||||||
millor = [esq, dre, regla];
|
best = [left, right, rule];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return millor;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
function plega(s) {
|
function folded(s) {
|
||||||
return s.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
return s.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function distancia(a, b) {
|
function distance(a, b) {
|
||||||
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i]);
|
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i]);
|
||||||
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
||||||
for (let i = 1; i <= a.length; i++) {
|
for (let i = 1; i <= a.length; i++) {
|
||||||
@ -152,27 +133,27 @@ function distancia(a, b) {
|
|||||||
return dp[a.length][b.length];
|
return dp[a.length][b.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Semblança amb l'original: una paraula correcta gairebe sempre
|
// Resemblance to the source: a correct word almost always resembles
|
||||||
// s'assembla al seu cognat en `es`/`en`; un enganxat, mai. Un afix
|
// its cognate in `en`/`es`; a glued one never does. A pure affix
|
||||||
// pur (1-4 lletres de mes o de menys) no compta: es justament la
|
// (1-4 letters more or less) doesn't count: that is exactly the
|
||||||
// forma de l'enganxat (`lapolitica` vs `politica`, `desdel` vs `desde`).
|
// shape of a glue (`lapolitica` vs `politica`, `desdel` vs `desde`).
|
||||||
function esCognat(tok, refToks) {
|
function isCognate(tok, refToks, elision) {
|
||||||
const base = tok.replace(/^[ldsmntc]['’]/, "");
|
const base = tok.replace(new RegExp(`^[${elision || "-"}]['’]`), "");
|
||||||
const t = plega(base);
|
const t = folded(base);
|
||||||
const llindar = t.length <= 4 ? 0 : 1;
|
const limit = t.length <= 4 ? 0 : 1;
|
||||||
for (const r of refToks) {
|
for (const r of refToks) {
|
||||||
const rt = plega(r);
|
const rt = folded(r);
|
||||||
if (Math.abs(rt.length - t.length) > Math.max(llindar, 4)) continue;
|
if (Math.abs(rt.length - t.length) > Math.max(limit, 4)) continue;
|
||||||
const d = distancia(t, rt);
|
const d = distance(t, rt);
|
||||||
if (d === 0) return true;
|
if (d === 0) return true;
|
||||||
if (d > llindar) continue;
|
if (d > limit) continue;
|
||||||
if (esAfix(t, rt)) continue;
|
if (isAffix(t, rt)) continue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function esAfix(t, rt) {
|
function isAffix(t, rt) {
|
||||||
const dif = Math.abs(t.length - rt.length);
|
const dif = Math.abs(t.length - rt.length);
|
||||||
if (dif < 1 || dif > 4) return false;
|
if (dif < 1 || dif > 4) return false;
|
||||||
return (
|
return (
|
||||||
@ -180,135 +161,188 @@ function esAfix(t, rt) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function refToks(textEn, textEs) {
|
function refToks(...texts) {
|
||||||
const toks = new Set();
|
const toks = new Set();
|
||||||
for (const t of tokenitza(textEn)) toks.add(t);
|
for (const text of texts) for (const t of tokenize(text)) toks.add(t);
|
||||||
for (const t of tokenitza(textEs)) toks.add(t);
|
|
||||||
return toks;
|
return toks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function contextNet(frag) {
|
function cleanContext(frag) {
|
||||||
return !/https?:|www\.|@|\|target:|\.mcp\.json/.test(frag);
|
return !/https?:|www\.|@|\|target:|\.mcp\.json/.test(frag);
|
||||||
}
|
}
|
||||||
|
|
||||||
function revisaPuntuacio(text) {
|
function checkPunctuation(text, brands) {
|
||||||
const trobats = [];
|
const found = [];
|
||||||
for (const m of text.matchAll(PUNT_RE)) {
|
for (const m of text.matchAll(PUNCT_RE)) {
|
||||||
const punt = m[0][0];
|
const punct = m[0][0];
|
||||||
const seg = m[1];
|
const next = m[1];
|
||||||
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
||||||
if (!contextNet(frag)) continue;
|
if (!cleanContext(frag)) continue;
|
||||||
if (m[0] === "](") continue; // enllaç markdown
|
if (m[0] === "](") continue; // markdown link
|
||||||
if (m[0] === ":s" && frag.includes("|target:")) continue; // [x|target:self]
|
if (m[0] === ":s" && frag.includes("|target:")) continue; // [x|target:self]
|
||||||
if (/%[sd]\.\(/.test(frag)) continue; // notació tècnica %s.(sufix)...
|
if (/%[sd]\.\(/.test(frag)) continue; // %s.(suffix)... notation
|
||||||
if (punt === "." && !/[A-ZÀÈÉÍÒÓÚÜ«"“(%$]/.test(seg)) continue;
|
if (punct === "." && !/[A-ZÀÈÉÍÒÓÚÜ«"“(%$]/.test(next)) continue;
|
||||||
trobats.push({ que: m[0], frag });
|
found.push({ what: m[0], frag });
|
||||||
}
|
}
|
||||||
for (const m of text.matchAll(CAMEL_RE)) {
|
for (const m of text.matchAll(CAMEL_RE)) {
|
||||||
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
||||||
if (MARQUES_OK.some((mk) => frag.includes(mk))) continue;
|
if (brands.some((mk) => frag.includes(mk))) continue;
|
||||||
trobats.push({ que: m[0], frag });
|
found.push({ what: m[0], frag });
|
||||||
}
|
}
|
||||||
for (const m of text.matchAll(PH_GLUED_RE)) {
|
for (const m of text.matchAll(PH_GLUED_RE)) {
|
||||||
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
||||||
trobats.push({ que: m[0], frag });
|
found.push({ what: m[0], frag });
|
||||||
}
|
}
|
||||||
for (const m of text.matchAll(/[a-zàèéíòóúüç·]d['’][0-9]/gu)) {
|
for (const m of text.matchAll(APOSTROPHE_DIGIT_RE)) {
|
||||||
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
const frag = text.slice(Math.max(0, m.index - 30), m.index + 32);
|
||||||
trobats.push({ que: m[0], frag });
|
found.push({ what: m[0], frag });
|
||||||
}
|
}
|
||||||
return trobats;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
function carrega(ruta) {
|
function loadFile(path) {
|
||||||
return fs.readFile(ruta).then((buf) => gt.po.parse(buf, "utf-8"));
|
return fs.readFile(path).then((buf) => gt.po.parse(buf, "utf-8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function revisa(locale) {
|
function parseCatalog(text, locale) {
|
||||||
const base = `./translations/${locale}.po`;
|
const words = {
|
||||||
const [dades, dadesEn, dadesEs] = await Promise.all([
|
functionWords: new Set(),
|
||||||
carrega(base),
|
commonWords: new Set(),
|
||||||
carrega("./translations/en.po"),
|
okWords: new Set(),
|
||||||
carrega("./translations/es.po").catch(() => null),
|
brands: [],
|
||||||
|
elision: "",
|
||||||
|
};
|
||||||
|
const sections = { function: 1, common: 1, ok: 1, brands: 1, elision: 1 };
|
||||||
|
let current = null;
|
||||||
|
for (const raw of text.split("\n")) {
|
||||||
|
const line = raw.trim();
|
||||||
|
if (line === "" || line.startsWith("#")) continue;
|
||||||
|
const sec = line.match(/^\[([a-z]+)\]$/);
|
||||||
|
if (sec) {
|
||||||
|
if (!sections[sec[1]]) {
|
||||||
|
throw new Error(`words.${locale}.txt: unknown section [${sec[1]}]`);
|
||||||
|
}
|
||||||
|
current = sec[1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!current) {
|
||||||
|
throw new Error(`words.${locale}.txt: word outside any section`);
|
||||||
|
}
|
||||||
|
if (current === "elision") {
|
||||||
|
if (!/^[a-z]+$/.test(line)) {
|
||||||
|
throw new Error(`words.${locale}.txt: bad [elision] line`);
|
||||||
|
}
|
||||||
|
words.elision += line;
|
||||||
|
} else if (current === "brands") {
|
||||||
|
words.brands.push(line);
|
||||||
|
} else {
|
||||||
|
words[`${current}Words`].add(line.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCatalog(locale) {
|
||||||
|
const path = `./scripts/check-translations/words.${locale}.txt`;
|
||||||
|
try {
|
||||||
|
return parseCatalog(await fs.readFile(path, "utf-8"), locale);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "ENOENT") return null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFuzzy(entry) {
|
||||||
|
return (entry.comments?.flag ?? "").split(/,\s*/).includes("fuzzy");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check(locale, words) {
|
||||||
|
const [data, dataEn, dataEs] = await Promise.all([
|
||||||
|
loadFile(`./translations/${locale}.po`),
|
||||||
|
loadFile("./translations/en.po"),
|
||||||
|
locale === "es" ? null : loadFile("./translations/es.po").catch(() => null),
|
||||||
]);
|
]);
|
||||||
const entrades = dades.translations[""];
|
const entries = data.translations[""];
|
||||||
const entradesEn = dadesEn.translations[""];
|
const entriesEn = dataEn.translations[""];
|
||||||
const entradesEs = dadesEs ? dadesEs.translations[""] : {};
|
const entriesEs = dataEs ? dataEs.translations[""] : {};
|
||||||
|
const brands = [...GENERIC_BRANDS, ...(words?.brands ?? [])];
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const avisos = [];
|
const warnings = [];
|
||||||
|
|
||||||
const freq = new Map();
|
const freq = new Map();
|
||||||
for (const [msgid, e] of Object.entries(entrades)) {
|
for (const [msgid, e] of Object.entries(entries)) {
|
||||||
if (msgid === "") continue;
|
if (msgid === "" || isFuzzy(e)) continue;
|
||||||
for (const t of e.msgstr)
|
for (const t of e.msgstr)
|
||||||
for (const w of tokenitza(t)) {
|
for (const w of tokenize(t)) {
|
||||||
freq.set(w, (freq.get(w) ?? 0) + 1);
|
freq.set(w, (freq.get(w) ?? 0) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [msgid, e] of Object.entries(entrades)) {
|
for (const [msgid, e] of Object.entries(entries)) {
|
||||||
if (msgid === "") continue;
|
if (msgid === "" || isFuzzy(e)) continue;
|
||||||
const textos = e.msgstr;
|
const texts = e.msgstr;
|
||||||
const eEn = entradesEn[msgid];
|
const eEn = entriesEn[msgid];
|
||||||
const textosEn = eEn ? eEn.msgstr : [];
|
const textsEn = eEn ? eEn.msgstr : [];
|
||||||
const eEs = entradesEs[msgid];
|
const eEs = entriesEs[msgid];
|
||||||
const textosEs = eEs ? eEs.msgstr : [];
|
const textsEs = eEs ? eEs.msgstr : [];
|
||||||
|
|
||||||
if (eEn?.msgid_plural && !e.msgid_plural) {
|
if (eEn?.msgid_plural && !e.msgid_plural) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${msgid}: l'original usa msgid_plural pero el ${locale} no te msgstr[0]/[1]`,
|
`${msgid}: source uses msgid_plural but ${locale} lacks msgstr[0]/[1]`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
textos.forEach((text, i) => {
|
texts.forEach((text, i) => {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
const textEn = textosEn[i] ?? "";
|
const textEn = textsEn[i] ?? "";
|
||||||
const textEs = textosEs[i] ?? "";
|
const textEs = textsEs[i] ?? "";
|
||||||
const refs = refToks(textEn, textEs);
|
const refs = refToks(textEn, textEs);
|
||||||
if (textEn) {
|
if (textEn) {
|
||||||
for (const re of PLACEHOLDER_RES) {
|
for (const re of PLACEHOLDER_RES) {
|
||||||
const nEn = compte(textEn, re);
|
const nEn = countIn(textEn, re);
|
||||||
const nCa = compte(text, re);
|
const nLoc = countIn(text, re);
|
||||||
if (nEn !== nCa) {
|
if (nEn !== nLoc) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${msgid}[${i}]: placeholders ${re.source}: en=${nEn} ${locale}=${nCa}`,
|
`${msgid}[${i}]: placeholder mismatch ${re.source}: en=${nEn} ${locale}=${nLoc}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const t of revisaPuntuacio(text)) {
|
for (const t of checkPunctuation(text, brands)) {
|
||||||
errors.push(
|
errors.push(
|
||||||
`${msgid}: puntuacio enganxada ${JSON.stringify(t.que)} ...${t.frag}...`,
|
`${msgid}: glued punctuation ${JSON.stringify(t.what)} ...${t.frag}...`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const tok of tokenitza(text)) {
|
if (!words) return;
|
||||||
|
for (const tok of tokenize(text)) {
|
||||||
if (SKIP_RE.test(tok)) continue;
|
if (SKIP_RE.test(tok)) continue;
|
||||||
if (PARAULES_OK.has(tok)) continue;
|
if (words.okWords.has(tok)) continue;
|
||||||
// Sense salt per frequencia del token sencer: un enganxat
|
// No whole-token frequency skip: a repeated glue
|
||||||
// repetit (`del'equip` x3) no s'ha d'auto-validar mai.
|
// (`del'equip` x3) must never validate itself by frequency.
|
||||||
const base = baseApostrof(tok);
|
const base = elisionBase(tok, words.elision);
|
||||||
if (base && partValida(base, freq)) continue;
|
if (base && validPart(base, words, freq)) continue;
|
||||||
if (esCognat(tok, refs)) continue;
|
if (isCognate(tok, refs, words.elision)) continue;
|
||||||
const part = millorParticio(tok, freq);
|
const part = bestSplit(tok, words, freq);
|
||||||
if (!part) continue;
|
if (!part) continue;
|
||||||
const [esq, dre, regla] = part;
|
const [left, right, rule] = part;
|
||||||
const linia = `${msgid}: possible enganxat ${JSON.stringify(tok)} -> ${esq} + ${dre}`;
|
const line = `${msgid}: glued word ${JSON.stringify(tok)} -> ${left} + ${right}`;
|
||||||
if (regla === "contingut") avisos.push(`${linia} (revisar)`);
|
if (rule === "content") warnings.push(`${line} (review)`);
|
||||||
else errors.push(linia);
|
else errors.push(line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { errors, avisos };
|
return { errors, warnings };
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIXTURES = [
|
const FIXTURES = [
|
||||||
// [text, esperaError]
|
// [text, expectsError]
|
||||||
["Els membres del'equip continuaran.", true],
|
["Els membres del'equip continuaran.", true],
|
||||||
["Accepteu lapolítica de privadesa.", true],
|
["Accepteu lapolítica de privadesa.", true],
|
||||||
["Si necessiteu més informació,contacteu amb nosaltres.", true],
|
["Si necessiteu més informació,contacteu amb nosaltres.", true],
|
||||||
["Revisions delPenpot disponibles.", true],
|
["Revisions delPenpot disponibles.", true],
|
||||||
["Seleccioneu Lowercase oCapitalize.", true],
|
["Seleccioneu Lowercase oCapitalize.", true],
|
||||||
["Cobreix fins a %seditors nous.", true],
|
["Cobreix fins a %seditors nous.", true],
|
||||||
|
["Bienvenido acasa nueva.", true],
|
||||||
["Les biblioteques compartides.", false],
|
["Les biblioteques compartides.", false],
|
||||||
["Edita el webhook.", false],
|
["Edita el webhook.", false],
|
||||||
["Emplenament del grup.", false],
|
["Emplenament del grup.", false],
|
||||||
@ -318,71 +352,87 @@ const FIXTURES = [
|
|||||||
["Desbloquegeu les funcions.", false],
|
["Desbloquegeu les funcions.", false],
|
||||||
["Atributs SVG importats.", false],
|
["Atributs SVG importats.", false],
|
||||||
["Commuta la negreta.", false],
|
["Commuta la negreta.", false],
|
||||||
|
["Bibliotecas compartidas.", false],
|
||||||
];
|
];
|
||||||
|
|
||||||
async function selfTest() {
|
async function selfTest(catalog) {
|
||||||
// Vocabulari minim per a les fixtures.
|
// Minimal vocabulary for the fixtures.
|
||||||
const freq = new Map(
|
const freq = new Map(
|
||||||
"els membres continuaran accepteu de privadesa si necessiteu més informació amb nosaltres revisions disponibles seleccioneu lowercase infrequent les biblioteques compartides edita el webhook emplenament del grup està desant fitxer components no es poden niar gira commuta la negreta desbloquegeu les funcions atributs svg importats horitzontalment podeu crear equip política"
|
"els membres continuaran accepteu de privadesa si necessiteu més informació amb nosaltres revisions disponibles seleccioneu lowercase infrequent les biblioteques compartides edita el webhook emplenament del grup està desant fitxer components no es poden niar gira commuta la negreta desbloquegeu les funcions atributs svg importats horitzontalment podeu crear equip política casa bibliotecas compartidas bienvenido nueva"
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((w) => [w, 3]),
|
.map((w) => [w, 3]),
|
||||||
);
|
);
|
||||||
let mal = 0;
|
const words = {
|
||||||
for (const [text, esperaError] of FIXTURES) {
|
functionWords: catalog.functionWords,
|
||||||
const trobats = [];
|
commonWords: new Set(),
|
||||||
for (const t of revisaPuntuacio(text)) trobats.push(`punt:${t.que}`);
|
okWords: new Set(),
|
||||||
for (const tok of tokenitza(text)) {
|
brands: [],
|
||||||
if (SKIP_RE.test(tok) || FUNCIO.has(tok)) continue;
|
elision: catalog.elision,
|
||||||
if (PARAULES_OK.has(tok)) continue;
|
};
|
||||||
if (partValida(tok, freq)) continue;
|
let bad = 0;
|
||||||
const base = baseApostrof(tok);
|
for (const [text, expectsError] of FIXTURES) {
|
||||||
if (base && partValida(base, freq)) continue;
|
const found = [];
|
||||||
const part = millorParticio(tok, freq);
|
for (const t of checkPunctuation(text, GENERIC_BRANDS))
|
||||||
if (part && part[2] !== "contingut") trobats.push(`tok:${tok}`);
|
found.push(`punct:${t.what}`);
|
||||||
|
for (const tok of tokenize(text)) {
|
||||||
|
if (SKIP_RE.test(tok) || words.functionWords.has(tok)) continue;
|
||||||
|
if (words.okWords.has(tok)) continue;
|
||||||
|
const base = elisionBase(tok, words.elision);
|
||||||
|
if (base && validPart(base, words, freq)) continue;
|
||||||
|
const part = bestSplit(tok, words, freq);
|
||||||
|
if (part && part[2] !== "content") found.push(`tok:${tok}`);
|
||||||
}
|
}
|
||||||
const hiHaError = trobats.length > 0;
|
if (found.length > 0 !== expectsError) {
|
||||||
if (hiHaError !== esperaError) {
|
|
||||||
console.error(
|
console.error(
|
||||||
`SELF-TEST FALLA: ${JSON.stringify(text)} esperava error=${esperaError}, trobats=${JSON.stringify(trobats)}`,
|
`SELF-TEST FAILED: ${JSON.stringify(text)} expected error=${expectsError}, found=${JSON.stringify(found)}`,
|
||||||
);
|
);
|
||||||
mal++;
|
bad++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mal > 0) process.exit(1);
|
if (bad > 0) process.exit(1);
|
||||||
console.log(`SELF-TEST OK: ${FIXTURES.length} casos`);
|
console.log(`SELF-TEST OK: ${FIXTURES.length} cases`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const opcions = getopts(process.argv.slice(2), {
|
const options = getopts(process.argv.slice(2), {
|
||||||
string: ["l"],
|
string: ["l"],
|
||||||
boolean: ["self-test", "h"],
|
boolean: ["self-test", "h"],
|
||||||
alias: { locale: ["l"], help: ["h"] },
|
alias: { locale: ["l"], help: ["h"] },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opcions.h) {
|
if (options.h) {
|
||||||
console.log(`QA de traduccions PO.
|
console.log(`PO translation QA.
|
||||||
Us: node ./scripts/check-translations.js [-l <locale>] [--self-test]
|
Usage: node ./scripts/check-translations.js [-l <locale>] [--self-test]
|
||||||
-l: locale a revisar (defecte: ca), des de frontend/
|
-l: locale to check (default: ca), from frontend/
|
||||||
--self-test: comprova el detector amb casos integrats`);
|
--self-test: validate the detector with built-in cases`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opcions["self-test"]) {
|
if (options["self-test"]) {
|
||||||
await selfTest();
|
const catalog = await loadCatalog("ca");
|
||||||
} else {
|
if (!catalog) {
|
||||||
const locale = opcions.l ?? opcions.locale ?? "ca";
|
console.error("SELF-TEST FAILED: cannot load words.ca.txt");
|
||||||
let dades;
|
|
||||||
try {
|
|
||||||
dades = await revisa(locale);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
`No s'ha pogut llegir translations/${locale}.po: ${err.message}`,
|
|
||||||
);
|
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
for (const w of dades.avisos) console.log(`avis: ${w}`);
|
await selfTest(catalog);
|
||||||
for (const e of dades.errors) console.error(`error: ${e}`);
|
} else {
|
||||||
|
const locale = options.l ?? options.locale ?? "ca";
|
||||||
|
const catalog = await loadCatalog(locale);
|
||||||
|
if (!catalog) {
|
||||||
|
console.log(
|
||||||
|
`note: no word catalog for '${locale}', lexical checks skipped`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await check(locale, catalog);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Could not read translations/${locale}.po: ${err.message}`);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
for (const w of result.warnings) console.log(`warn: ${w}`);
|
||||||
|
for (const e of result.errors) console.error(`error: ${e}`);
|
||||||
console.log(
|
console.log(
|
||||||
`${locale}: ${dades.errors.length} errors, ${dades.avisos.length} avisos`,
|
`${locale}: ${result.errors.length} errors, ${result.warnings.length} warnings`,
|
||||||
);
|
);
|
||||||
process.exit(dades.errors.length > 0 ? 1 : 0);
|
process.exit(result.errors.length > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
505
frontend/scripts/check-translations/words.ca.txt
Normal file
505
frontend/scripts/check-translations/words.ca.txt
Normal file
@ -0,0 +1,505 @@
|
|||||||
|
# Word catalog for check-translations.js (Catalan).
|
||||||
|
# Comment lines start with #. Blank lines are ignored.
|
||||||
|
# [function] short function words; a token starting with one of
|
||||||
|
# these is likely missing a space (del-equip style).
|
||||||
|
# [common] ordinary words used to judge the other half of a split.
|
||||||
|
# [ok] correct words the splitter would flag; never report
|
||||||
|
# these. Add new valid words here when the gate trips.
|
||||||
|
# [brands] extra locale-specific brands/terms kept as-is.
|
||||||
|
# [elision] letters elided with an apostrophe.
|
||||||
|
|
||||||
|
[elision]
|
||||||
|
ldsmntc
|
||||||
|
|
||||||
|
[function]
|
||||||
|
i
|
||||||
|
o
|
||||||
|
a
|
||||||
|
e
|
||||||
|
de
|
||||||
|
del
|
||||||
|
dels
|
||||||
|
la
|
||||||
|
el
|
||||||
|
els
|
||||||
|
les
|
||||||
|
lo
|
||||||
|
un
|
||||||
|
una
|
||||||
|
uns
|
||||||
|
unes
|
||||||
|
al
|
||||||
|
als
|
||||||
|
en
|
||||||
|
amb
|
||||||
|
per
|
||||||
|
pel
|
||||||
|
pels
|
||||||
|
que
|
||||||
|
com
|
||||||
|
no
|
||||||
|
ni
|
||||||
|
si
|
||||||
|
ja
|
||||||
|
se
|
||||||
|
es
|
||||||
|
ho
|
||||||
|
hi
|
||||||
|
li
|
||||||
|
me
|
||||||
|
te
|
||||||
|
ne
|
||||||
|
em
|
||||||
|
et
|
||||||
|
us
|
||||||
|
vos
|
||||||
|
ens
|
||||||
|
son
|
||||||
|
són
|
||||||
|
és
|
||||||
|
més
|
||||||
|
mes
|
||||||
|
tot
|
||||||
|
tota
|
||||||
|
molt
|
||||||
|
tan
|
||||||
|
tant
|
||||||
|
on
|
||||||
|
quan
|
||||||
|
perquè
|
||||||
|
pero
|
||||||
|
però
|
||||||
|
doncs
|
||||||
|
fins
|
||||||
|
entre
|
||||||
|
sobre
|
||||||
|
sota
|
||||||
|
cap
|
||||||
|
cada
|
||||||
|
altre
|
||||||
|
seva
|
||||||
|
seu
|
||||||
|
meva
|
||||||
|
teu
|
||||||
|
nostre
|
||||||
|
vostre
|
||||||
|
aquest
|
||||||
|
aquesta
|
||||||
|
això
|
||||||
|
allò
|
||||||
|
jo
|
||||||
|
tu
|
||||||
|
ell
|
||||||
|
ella
|
||||||
|
nosaltres
|
||||||
|
vosaltres
|
||||||
|
ells
|
||||||
|
|
||||||
|
[common]
|
||||||
|
corregir
|
||||||
|
poder
|
||||||
|
obtenir
|
||||||
|
mantindran
|
||||||
|
targeta
|
||||||
|
controladors
|
||||||
|
importants
|
||||||
|
futura
|
||||||
|
donant
|
||||||
|
tingui
|
||||||
|
informar
|
||||||
|
diversitat
|
||||||
|
continuï
|
||||||
|
juntament
|
||||||
|
revocaran
|
||||||
|
habilitar
|
||||||
|
desament
|
||||||
|
autenticació
|
||||||
|
family
|
||||||
|
aplicació
|
||||||
|
còpies
|
||||||
|
configurat
|
||||||
|
necessita
|
||||||
|
conflictes
|
||||||
|
trigar
|
||||||
|
res
|
||||||
|
directament
|
||||||
|
enviï
|
||||||
|
correccions
|
||||||
|
admet
|
||||||
|
exportacions
|
||||||
|
aplicacions
|
||||||
|
càrrec
|
||||||
|
envia
|
||||||
|
enllaç
|
||||||
|
actuals
|
||||||
|
ajuda
|
||||||
|
moment
|
||||||
|
valor
|
||||||
|
equips
|
||||||
|
conjunt
|
||||||
|
projecte
|
||||||
|
fitxer
|
||||||
|
compte
|
||||||
|
usuari
|
||||||
|
persona
|
||||||
|
persones
|
||||||
|
cosa
|
||||||
|
temps
|
||||||
|
part
|
||||||
|
text
|
||||||
|
nom
|
||||||
|
contrasenya
|
||||||
|
sessió
|
||||||
|
idioma
|
||||||
|
versió
|
||||||
|
canvi
|
||||||
|
arxiu
|
||||||
|
error
|
||||||
|
avís
|
||||||
|
filtre
|
||||||
|
cerca
|
||||||
|
vista
|
||||||
|
pestanya
|
||||||
|
botó
|
||||||
|
camp
|
||||||
|
llista
|
||||||
|
taula
|
||||||
|
imatge
|
||||||
|
forma
|
||||||
|
capa
|
||||||
|
fons
|
||||||
|
color
|
||||||
|
mida
|
||||||
|
data
|
||||||
|
correu
|
||||||
|
propietari
|
||||||
|
propietària
|
||||||
|
membre
|
||||||
|
opcions
|
||||||
|
prova
|
||||||
|
suprimireu
|
||||||
|
interactuïn
|
||||||
|
ajudarem
|
||||||
|
quedi
|
||||||
|
configurat
|
||||||
|
permetre
|
||||||
|
continueu
|
||||||
|
breu
|
||||||
|
manteniment
|
||||||
|
present
|
||||||
|
començar
|
||||||
|
donant
|
||||||
|
css
|
||||||
|
font
|
||||||
|
prioritzar
|
||||||
|
suborganitzacions
|
||||||
|
podreu
|
||||||
|
poden
|
||||||
|
facturació
|
||||||
|
|
||||||
|
[ok]
|
||||||
|
ajudarem
|
||||||
|
aplana
|
||||||
|
coincideixi
|
||||||
|
coma
|
||||||
|
comes
|
||||||
|
comprova
|
||||||
|
comprovar
|
||||||
|
comprovar-ho
|
||||||
|
comuna
|
||||||
|
convidarem
|
||||||
|
estarem
|
||||||
|
existeixen
|
||||||
|
existeixi
|
||||||
|
fase
|
||||||
|
gratuïta
|
||||||
|
interna
|
||||||
|
mateixa
|
||||||
|
meves
|
||||||
|
molta
|
||||||
|
oberta
|
||||||
|
obertes
|
||||||
|
permeten
|
||||||
|
permeti
|
||||||
|
segura
|
||||||
|
targeta
|
||||||
|
teus
|
||||||
|
usa
|
||||||
|
usen
|
||||||
|
niar
|
||||||
|
negreta
|
||||||
|
emplena
|
||||||
|
emplenament
|
||||||
|
edita
|
||||||
|
selector
|
||||||
|
atributs
|
||||||
|
sobreescriuran
|
||||||
|
compartides
|
||||||
|
desenvolupadors
|
||||||
|
administradors
|
||||||
|
desbloquejar
|
||||||
|
desbloquegeu
|
||||||
|
opcionalment
|
||||||
|
autoreferència
|
||||||
|
especificant
|
||||||
|
previsualització
|
||||||
|
previsualitza
|
||||||
|
predeterminat
|
||||||
|
predeterminats
|
||||||
|
predeterminada
|
||||||
|
predefinides
|
||||||
|
predefinida
|
||||||
|
desactivades
|
||||||
|
seleccionada
|
||||||
|
seleccionades
|
||||||
|
descarregueu
|
||||||
|
centralitzat
|
||||||
|
horitzontalment
|
||||||
|
verticalment
|
||||||
|
multijugador
|
||||||
|
multiorganització
|
||||||
|
complementari
|
||||||
|
multiplicador
|
||||||
|
actualitzarà
|
||||||
|
activades
|
||||||
|
activador
|
||||||
|
cancel·laran
|
||||||
|
comptaran
|
||||||
|
configurar
|
||||||
|
confirmar-ho
|
||||||
|
conservaran
|
||||||
|
desagrupa
|
||||||
|
desbloqueja
|
||||||
|
desenganxa
|
||||||
|
despublica
|
||||||
|
despublicar
|
||||||
|
desselecciona-ho
|
||||||
|
duplicades
|
||||||
|
envia-ho
|
||||||
|
importada
|
||||||
|
importades
|
||||||
|
interactius
|
||||||
|
intercanvia
|
||||||
|
lliscament
|
||||||
|
lliuraran
|
||||||
|
migracions
|
||||||
|
mixte
|
||||||
|
personalitzeu-les
|
||||||
|
plantilles
|
||||||
|
privadesa
|
||||||
|
realitzant
|
||||||
|
repositori
|
||||||
|
restauraran
|
||||||
|
selecciona-ho
|
||||||
|
superposa
|
||||||
|
transferiu
|
||||||
|
unir-se
|
||||||
|
usar-los
|
||||||
|
autodesat
|
||||||
|
demanar-ho
|
||||||
|
desactivar-los
|
||||||
|
abandonar-la
|
||||||
|
accedir-hi
|
||||||
|
accelereu
|
||||||
|
activar-los
|
||||||
|
actualitzeu
|
||||||
|
adoptarà
|
||||||
|
afegir-hi
|
||||||
|
afegir-ne
|
||||||
|
afegiu
|
||||||
|
afegiu-ne
|
||||||
|
agrupant-la
|
||||||
|
ajudar-nos
|
||||||
|
ajudar-vos
|
||||||
|
ajudeu-nos
|
||||||
|
ajudieu-nos
|
||||||
|
ajustar-lo
|
||||||
|
ajusteu
|
||||||
|
ajustis
|
||||||
|
alineació
|
||||||
|
amplia
|
||||||
|
ampliada
|
||||||
|
amplieu
|
||||||
|
animacions
|
||||||
|
aplicant
|
||||||
|
apliqueu
|
||||||
|
arrossegueu
|
||||||
|
assegurar-vos
|
||||||
|
avançar-vos
|
||||||
|
avisos
|
||||||
|
baixades
|
||||||
|
cancel·lada
|
||||||
|
cancel·lant
|
||||||
|
cercador
|
||||||
|
coincidents
|
||||||
|
col·laboració
|
||||||
|
col·laborar
|
||||||
|
commuteu
|
||||||
|
compatibilitat
|
||||||
|
concedir-hi
|
||||||
|
consumeixin
|
||||||
|
conèixer-vos
|
||||||
|
definir-ne
|
||||||
|
definiu
|
||||||
|
depuració
|
||||||
|
desbloca
|
||||||
|
descarta-ho
|
||||||
|
descriviu
|
||||||
|
desemmascara
|
||||||
|
deshabilita
|
||||||
|
deshabilitada
|
||||||
|
deshabilitades
|
||||||
|
desplaçar
|
||||||
|
desvincula
|
||||||
|
dissenyeu
|
||||||
|
editar-lo
|
||||||
|
editen
|
||||||
|
editeu
|
||||||
|
eliminar-lo
|
||||||
|
el·lipse
|
||||||
|
el·lipses
|
||||||
|
emplenats
|
||||||
|
encabir-ho
|
||||||
|
enfosqueix
|
||||||
|
escriviu-nos
|
||||||
|
espaiat
|
||||||
|
espaiats
|
||||||
|
especifiqueu
|
||||||
|
estils
|
||||||
|
expliqueu-nos
|
||||||
|
exporta
|
||||||
|
exportant
|
||||||
|
exportar
|
||||||
|
flexibilitat
|
||||||
|
gaudireu
|
||||||
|
il·limitat
|
||||||
|
il·limitats
|
||||||
|
il·lumina
|
||||||
|
il·lustracions
|
||||||
|
importats
|
||||||
|
incrusta
|
||||||
|
inhabilitada
|
||||||
|
inicieu
|
||||||
|
insereix
|
||||||
|
inspecciona
|
||||||
|
inspeccionar-ne
|
||||||
|
instal·la
|
||||||
|
instal·lació
|
||||||
|
instal·lada
|
||||||
|
instal·lades
|
||||||
|
instal·lat
|
||||||
|
instantànies
|
||||||
|
integracions
|
||||||
|
intentar-ho
|
||||||
|
marca-ho
|
||||||
|
migració
|
||||||
|
milloreu
|
||||||
|
milloreu-ho
|
||||||
|
notificacions
|
||||||
|
obsolet
|
||||||
|
obteniu
|
||||||
|
ometeu-ho
|
||||||
|
omplir-ho
|
||||||
|
opacitat
|
||||||
|
orientar-vos
|
||||||
|
personalitzada
|
||||||
|
personalitzades
|
||||||
|
pestanya
|
||||||
|
porta-ho
|
||||||
|
porta-retalls
|
||||||
|
promoveu
|
||||||
|
prototipar
|
||||||
|
prototipatge
|
||||||
|
publicar-la
|
||||||
|
reassignar
|
||||||
|
reassigneu
|
||||||
|
remapejant
|
||||||
|
remapeja
|
||||||
|
remapejar
|
||||||
|
silenciats
|
||||||
|
sol·licitat
|
||||||
|
sol·licitud
|
||||||
|
sol·licituds
|
||||||
|
suprimiran
|
||||||
|
tipogràfic
|
||||||
|
tipogràfica
|
||||||
|
tipogràfics
|
||||||
|
tipogràfiques
|
||||||
|
torna-ho
|
||||||
|
valorant
|
||||||
|
vinculades
|
||||||
|
vincular
|
||||||
|
visiteu
|
||||||
|
volteja
|
||||||
|
unir-s'hi
|
||||||
|
l'opacitat
|
||||||
|
l'espaiat
|
||||||
|
l'interlineat
|
||||||
|
explora'n
|
||||||
|
edita'l
|
||||||
|
desant
|
||||||
|
aquesta
|
||||||
|
aquestes
|
||||||
|
baixa
|
||||||
|
capa
|
||||||
|
capes
|
||||||
|
compartir
|
||||||
|
comproveu
|
||||||
|
desa
|
||||||
|
deseu
|
||||||
|
dreta
|
||||||
|
esteu
|
||||||
|
files
|
||||||
|
fixa
|
||||||
|
inicia
|
||||||
|
inicials
|
||||||
|
meus
|
||||||
|
només
|
||||||
|
nous
|
||||||
|
noves
|
||||||
|
pel
|
||||||
|
perfil
|
||||||
|
perquè
|
||||||
|
pes
|
||||||
|
pla
|
||||||
|
seus
|
||||||
|
totes
|
||||||
|
treballo
|
||||||
|
una
|
||||||
|
vosaltres
|
||||||
|
vàlida
|
||||||
|
vàlides
|
||||||
|
desactiva
|
||||||
|
desactivat
|
||||||
|
desactivada
|
||||||
|
desconnecta
|
||||||
|
descobreix
|
||||||
|
connectades
|
||||||
|
alguna
|
||||||
|
canvia
|
||||||
|
compost
|
||||||
|
comprovant
|
||||||
|
del
|
||||||
|
dels
|
||||||
|
des
|
||||||
|
enviarem
|
||||||
|
mixtes
|
||||||
|
noms
|
||||||
|
quina
|
||||||
|
senzilla
|
||||||
|
uneix
|
||||||
|
ves
|
||||||
|
vista
|
||||||
|
vés
|
||||||
|
desconegut
|
||||||
|
fixa
|
||||||
|
pel
|
||||||
|
pels
|
||||||
|
tota
|
||||||
|
comentari
|
||||||
|
comunitat
|
||||||
|
set
|
||||||
|
desvinculat
|
||||||
|
separades
|
||||||
|
|
||||||
|
[brands]
|
||||||
321
frontend/scripts/check-translations/words.es.txt
Normal file
321
frontend/scripts/check-translations/words.es.txt
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# Word catalog for check-translations.js (Spanish).
|
||||||
|
# Comment lines start with #. Blank lines are ignored.
|
||||||
|
# [function] short function words; a token starting with one of
|
||||||
|
# these is likely missing a space.
|
||||||
|
# [common] ordinary words used to judge the other half of a split.
|
||||||
|
# [ok] correct words the splitter would flag; never report
|
||||||
|
# these. Add new valid words here when the gate trips.
|
||||||
|
# [brands] extra locale-specific brands/terms kept as-is.
|
||||||
|
# [elision] letters elided with an apostrophe (Spanish has none).
|
||||||
|
|
||||||
|
[elision]
|
||||||
|
|
||||||
|
[function]
|
||||||
|
el
|
||||||
|
la
|
||||||
|
los
|
||||||
|
las
|
||||||
|
un
|
||||||
|
una
|
||||||
|
unos
|
||||||
|
unas
|
||||||
|
al
|
||||||
|
del
|
||||||
|
de
|
||||||
|
en
|
||||||
|
y
|
||||||
|
e
|
||||||
|
o
|
||||||
|
u
|
||||||
|
que
|
||||||
|
como
|
||||||
|
con
|
||||||
|
por
|
||||||
|
para
|
||||||
|
sin
|
||||||
|
sobre
|
||||||
|
entre
|
||||||
|
hasta
|
||||||
|
desde
|
||||||
|
donde
|
||||||
|
cuando
|
||||||
|
porque
|
||||||
|
pues
|
||||||
|
si
|
||||||
|
no
|
||||||
|
ni
|
||||||
|
mas
|
||||||
|
más
|
||||||
|
muy
|
||||||
|
tan
|
||||||
|
tanto
|
||||||
|
todo
|
||||||
|
todos
|
||||||
|
toda
|
||||||
|
todas
|
||||||
|
cada
|
||||||
|
otro
|
||||||
|
otra
|
||||||
|
otros
|
||||||
|
otras
|
||||||
|
este
|
||||||
|
esta
|
||||||
|
estos
|
||||||
|
estas
|
||||||
|
ese
|
||||||
|
esa
|
||||||
|
eso
|
||||||
|
aquel
|
||||||
|
aquella
|
||||||
|
aquello
|
||||||
|
mi
|
||||||
|
mis
|
||||||
|
tu
|
||||||
|
tus
|
||||||
|
su
|
||||||
|
sus
|
||||||
|
nuestro
|
||||||
|
nuestra
|
||||||
|
nuestros
|
||||||
|
nuestras
|
||||||
|
vuestro
|
||||||
|
vuestra
|
||||||
|
vuestros
|
||||||
|
vuestras
|
||||||
|
me
|
||||||
|
te
|
||||||
|
se
|
||||||
|
nos
|
||||||
|
os
|
||||||
|
le
|
||||||
|
les
|
||||||
|
lo
|
||||||
|
yo
|
||||||
|
tú
|
||||||
|
él
|
||||||
|
ella
|
||||||
|
ello
|
||||||
|
nosotros
|
||||||
|
vosotros
|
||||||
|
ellos
|
||||||
|
ellas
|
||||||
|
esto
|
||||||
|
eso
|
||||||
|
aquello
|
||||||
|
algo
|
||||||
|
alguien
|
||||||
|
nada
|
||||||
|
nadie
|
||||||
|
quien
|
||||||
|
quienes
|
||||||
|
cual
|
||||||
|
cuales
|
||||||
|
cuanto
|
||||||
|
cuanta
|
||||||
|
cuantos
|
||||||
|
cuantas
|
||||||
|
|
||||||
|
[common]
|
||||||
|
habilitar
|
||||||
|
crear
|
||||||
|
eliminar
|
||||||
|
guardar
|
||||||
|
abrir
|
||||||
|
cerrar
|
||||||
|
enviar
|
||||||
|
compartir
|
||||||
|
invitar
|
||||||
|
mover
|
||||||
|
copiar
|
||||||
|
pegar
|
||||||
|
subir
|
||||||
|
bajar
|
||||||
|
editar
|
||||||
|
configurar
|
||||||
|
activar
|
||||||
|
desactivar
|
||||||
|
permitir
|
||||||
|
necesitar
|
||||||
|
poder
|
||||||
|
deber
|
||||||
|
querer
|
||||||
|
saber
|
||||||
|
hacer
|
||||||
|
poner
|
||||||
|
salir
|
||||||
|
entrar
|
||||||
|
volver
|
||||||
|
llegar
|
||||||
|
pasar
|
||||||
|
quedar
|
||||||
|
dar
|
||||||
|
estar
|
||||||
|
ser
|
||||||
|
haber
|
||||||
|
tener
|
||||||
|
ir
|
||||||
|
ver
|
||||||
|
usar
|
||||||
|
buscar
|
||||||
|
cambiar
|
||||||
|
seguir
|
||||||
|
encontrar
|
||||||
|
mostrar
|
||||||
|
ocultar
|
||||||
|
añadir
|
||||||
|
quitar
|
||||||
|
seleccionar
|
||||||
|
aplicar
|
||||||
|
cancelar
|
||||||
|
aceptar
|
||||||
|
probar
|
||||||
|
comprobar
|
||||||
|
generar
|
||||||
|
descargar
|
||||||
|
equipo
|
||||||
|
archivo
|
||||||
|
proyecto
|
||||||
|
cuenta
|
||||||
|
nombre
|
||||||
|
correo
|
||||||
|
error
|
||||||
|
aviso
|
||||||
|
ventana
|
||||||
|
página
|
||||||
|
botón
|
||||||
|
campo
|
||||||
|
lista
|
||||||
|
tabla
|
||||||
|
imagen
|
||||||
|
forma
|
||||||
|
capa
|
||||||
|
color
|
||||||
|
tamaño
|
||||||
|
fecha
|
||||||
|
enlace
|
||||||
|
nuevo
|
||||||
|
nueva
|
||||||
|
grande
|
||||||
|
pequeño
|
||||||
|
bueno
|
||||||
|
actual
|
||||||
|
siguiente
|
||||||
|
anterior
|
||||||
|
mismo
|
||||||
|
otra
|
||||||
|
primer
|
||||||
|
último
|
||||||
|
único
|
||||||
|
libre
|
||||||
|
público
|
||||||
|
|
||||||
|
[ok]
|
||||||
|
abandonarla
|
||||||
|
abrirlo
|
||||||
|
activarlos
|
||||||
|
actuales
|
||||||
|
actualizada
|
||||||
|
adicionales
|
||||||
|
administradores
|
||||||
|
algunos
|
||||||
|
alto
|
||||||
|
aplicarse
|
||||||
|
asignarse
|
||||||
|
aun
|
||||||
|
aunque
|
||||||
|
autoguardada
|
||||||
|
autoguardado
|
||||||
|
autoreferencia
|
||||||
|
ayudarnos
|
||||||
|
ayudarte
|
||||||
|
borradores
|
||||||
|
borrarlo
|
||||||
|
cambiada
|
||||||
|
caracteres
|
||||||
|
como
|
||||||
|
comparte
|
||||||
|
compartir
|
||||||
|
comprobar
|
||||||
|
comprueba
|
||||||
|
compuesto
|
||||||
|
comunidad
|
||||||
|
conectarlo
|
||||||
|
conectarse
|
||||||
|
conjunto
|
||||||
|
conocerte
|
||||||
|
consigue
|
||||||
|
contactarnos
|
||||||
|
contener
|
||||||
|
contiene
|
||||||
|
contexto
|
||||||
|
danos
|
||||||
|
demás
|
||||||
|
desactivarlos
|
||||||
|
deseleccionar
|
||||||
|
desuscripción
|
||||||
|
durante
|
||||||
|
editores
|
||||||
|
eje
|
||||||
|
eliminarlo
|
||||||
|
eliminarlos
|
||||||
|
ella
|
||||||
|
eso
|
||||||
|
esos
|
||||||
|
este
|
||||||
|
estoy
|
||||||
|
estándar
|
||||||
|
expirada
|
||||||
|
funcionalidades
|
||||||
|
granulares
|
||||||
|
hay
|
||||||
|
hubo
|
||||||
|
importada
|
||||||
|
iniciarse
|
||||||
|
invitaciones
|
||||||
|
lectores
|
||||||
|
letras
|
||||||
|
manifiesto
|
||||||
|
menos
|
||||||
|
nosotros
|
||||||
|
otras
|
||||||
|
pero
|
||||||
|
personales
|
||||||
|
peso
|
||||||
|
primero
|
||||||
|
principales
|
||||||
|
propiedades
|
||||||
|
proveedores
|
||||||
|
publicarla
|
||||||
|
publicarlo
|
||||||
|
puesto
|
||||||
|
regenerada
|
||||||
|
restaurarlo
|
||||||
|
restaurarlos
|
||||||
|
seleccionada
|
||||||
|
selector
|
||||||
|
servidores
|
||||||
|
sino
|
||||||
|
solicitudes
|
||||||
|
soportada
|
||||||
|
sufijo
|
||||||
|
tanto
|
||||||
|
temas
|
||||||
|
tenerte
|
||||||
|
tipográfico
|
||||||
|
tono
|
||||||
|
unidades
|
||||||
|
unir
|
||||||
|
unirse
|
||||||
|
uno
|
||||||
|
unos
|
||||||
|
usarlas
|
||||||
|
utilizada
|
||||||
|
verla
|
||||||
|
versiones
|
||||||
|
verticales
|
||||||
|
vincularse
|
||||||
|
visuales
|
||||||
|
volverlo
|
||||||
|
porque
|
||||||
|
|
||||||
|
[brands]
|
||||||
@ -112,7 +112,7 @@ msgstr "La contraseña ha sido cambiada"
|
|||||||
#: src/app/main/ui/auth/recovery_request.cljs:50
|
#: src/app/main/ui/auth/recovery_request.cljs:50
|
||||||
msgid "auth.notifications.profile-not-verified"
|
msgid "auth.notifications.profile-not-verified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"El perfil aun no ha sido verificado, por favor valida el perfil antes de "
|
"El perfil aún no ha sido verificado, por favor valida el perfil antes de "
|
||||||
"continuar."
|
"continuar."
|
||||||
|
|
||||||
#: src/app/main/ui/auth/recovery_request.cljs:33
|
#: src/app/main/ui/auth/recovery_request.cljs:33
|
||||||
@ -196,8 +196,8 @@ msgstr "Términos de servicio"
|
|||||||
#, unused
|
#, unused
|
||||||
msgid "auth.terms-privacy-agreement"
|
msgid "auth.terms-privacy-agreement"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Al crear una nueva cuenta, aceptas nuestros [términos de servicio](%s) y "
|
"Al crear una nueva cuenta, aceptas nuestros términos de servicio y política "
|
||||||
"[política de privacidad](%s)."
|
"de privacidad."
|
||||||
|
|
||||||
#: src/app/main/ui/auth/register.cljs:238
|
#: src/app/main/ui/auth/register.cljs:238
|
||||||
#, unused
|
#, unused
|
||||||
@ -1790,7 +1790,7 @@ msgstr "Parece que no es una imagen válida."
|
|||||||
msgid "errors.member-is-muted"
|
msgid "errors.member-is-muted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"El perfil que esta invitando tiene los emails silenciados (por reportes de "
|
"El perfil que esta invitando tiene los emails silenciados (por reportes de "
|
||||||
"spam o alto indice de rebote)."
|
"spam o alto índice de rebote)."
|
||||||
|
|
||||||
#: src/app/main/errors.cljs:483
|
#: src/app/main/errors.cljs:483
|
||||||
msgid "errors.migration-in-progress"
|
msgid "errors.migration-in-progress"
|
||||||
@ -1831,7 +1831,7 @@ msgstr "El perfil esta blockeado"
|
|||||||
#: src/app/main/ui/auth/recovery_request.cljs:53, src/app/main/ui/dashboard/team.cljs:225, src/app/main/ui/dashboard/team.cljs:731, src/app/main/ui/dashboard/team.cljs:1108, src/app/main/ui/onboarding/team_choice.cljs:109, src/app/main/ui/settings/change_email.cljs:33
|
#: src/app/main/ui/auth/recovery_request.cljs:53, src/app/main/ui/dashboard/team.cljs:225, src/app/main/ui/dashboard/team.cljs:731, src/app/main/ui/dashboard/team.cljs:1108, src/app/main/ui/onboarding/team_choice.cljs:109, src/app/main/ui/settings/change_email.cljs:33
|
||||||
msgid "errors.profile-is-muted"
|
msgid "errors.profile-is-muted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tu perfil tiene los emails silenciados (por reportes de spam o alto indice "
|
"Tu perfil tiene los emails silenciados (por reportes de spam o alto índice "
|
||||||
"de rebote)."
|
"de rebote)."
|
||||||
|
|
||||||
#: src/app/main/data/auth.cljs:346, src/app/main/ui/auth/register.cljs:91
|
#: src/app/main/data/auth.cljs:346, src/app/main/ui/auth/register.cljs:91
|
||||||
@ -2609,7 +2609,7 @@ msgstr "Crear nuevo token de acceso"
|
|||||||
|
|
||||||
#: src/app/main/ui/settings/integrations.cljs:632
|
#: src/app/main/ui/settings/integrations.cljs:632
|
||||||
msgid "integrations.access-tokens.empty.add-one"
|
msgid "integrations.access-tokens.empty.add-one"
|
||||||
msgstr "Pulsa el botón \"Crear nuevo token de accesso\" para generar uno."
|
msgstr "Pulsa el botón \"Crear nuevo token de acceso\" para generar uno."
|
||||||
|
|
||||||
#: src/app/main/ui/settings/integrations.cljs:631
|
#: src/app/main/ui/settings/integrations.cljs:631
|
||||||
msgid "integrations.access-tokens.empty.no-access-tokens"
|
msgid "integrations.access-tokens.empty.no-access-tokens"
|
||||||
@ -2622,7 +2622,7 @@ msgstr "Tokens de acceso personales"
|
|||||||
#: src/app/main/ui/settings/integrations.cljs:611
|
#: src/app/main/ui/settings/integrations.cljs:611
|
||||||
msgid "integrations.access-tokens.personal.description"
|
msgid "integrations.access-tokens.personal.description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Los tokens de accesso personales funcionan como una alternativa a nuestro "
|
"Los tokens de acceso personales funcionan como una alternativa a nuestro "
|
||||||
"sistema de autenticación usuario/password y se pueden usar para permitir a "
|
"sistema de autenticación usuario/password y se pueden usar para permitir a "
|
||||||
"otras aplicaciones acceso a la API interna de Penpot"
|
"otras aplicaciones acceso a la API interna de Penpot"
|
||||||
|
|
||||||
@ -2632,7 +2632,7 @@ msgstr "Copiar al portapapeles"
|
|||||||
|
|
||||||
#: src/app/main/ui/settings/integrations.cljs:253
|
#: src/app/main/ui/settings/integrations.cljs:253
|
||||||
msgid "integrations.create-access-token.title"
|
msgid "integrations.create-access-token.title"
|
||||||
msgstr "Crear token de accesso"
|
msgstr "Crear token de acceso"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/integrations.cljs:252
|
#: src/app/main/ui/settings/integrations.cljs:252
|
||||||
msgid "integrations.create-access-token.title.created"
|
msgid "integrations.create-access-token.title.created"
|
||||||
@ -8426,19 +8426,19 @@ msgstr "Opciones avanzadas"
|
|||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:695, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:702
|
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:695, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:702
|
||||||
msgid "workspace.options.layout-item.layout-item-max-h"
|
msgid "workspace.options.layout-item.layout-item-max-h"
|
||||||
msgstr "Altura.Max"
|
msgstr "Altura máx."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:633, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:640
|
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:633, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:640
|
||||||
msgid "workspace.options.layout-item.layout-item-max-w"
|
msgid "workspace.options.layout-item.layout-item-max-w"
|
||||||
msgstr "Ancho.Max"
|
msgstr "Ancho máx."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:664, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:672
|
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:664, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:672
|
||||||
msgid "workspace.options.layout-item.layout-item-min-h"
|
msgid "workspace.options.layout-item.layout-item-min-h"
|
||||||
msgstr "Altura.Min"
|
msgstr "Altura mín."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:600, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:609
|
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:600, src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:609
|
||||||
msgid "workspace.options.layout-item.layout-item-min-w"
|
msgid "workspace.options.layout-item.layout-item-min-w"
|
||||||
msgstr "Ancho.Min"
|
msgstr "Ancho mín."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
|
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
|
||||||
#, unused
|
#, unused
|
||||||
@ -9706,7 +9706,7 @@ msgstr "Nombre del grupo"
|
|||||||
#: src/app/main/ui/workspace/tokens/sets.cljs
|
#: src/app/main/ui/workspace/tokens/sets.cljs
|
||||||
#, unused
|
#, unused
|
||||||
msgid "workspace.tokens.grouping-set-alert"
|
msgid "workspace.tokens.grouping-set-alert"
|
||||||
msgstr "La agrupación de sets aun no está soportada."
|
msgstr "La agrupación de sets aún no está soportada."
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/import/modal.cljs:232
|
#: src/app/main/ui/workspace/tokens/import/modal.cljs:232
|
||||||
msgid "workspace.tokens.import-button-prefix"
|
msgid "workspace.tokens.import-button-prefix"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user