Phase 21: Petal learns to be an English+Portuguese pair
The plan said "Hunspell pt-PT vendored like en-US". Measuring that first is what saved it: nspell expands affixes eagerly on construction, and European Portuguese's 1,340 rules over 44,257 stems want over a gigabyte of browser heap — ~340 MB for the first 12,000 entries, and no return at all after three minutes on the whole file. So the expansion runs once at build time instead: 1,039,058 forms, 2.66 MB gzipped, read by the same nspell in 842 ms. The obvious npm package would also have shipped the wrong language. Both dictionary-pt and dictionary-pt-br carry VERO, the Brazilian word list, so vendoring by name puts pt-BR spellings behind a pt-PT label — the drift SUGGESTIONS §3 warns about, arriving through the packaging where no reviewer can see it. The source is Projecto Natura's, and the build script now asserts the fault lines (receção in, recepção out) before writing anything. Spellcheck consults both dictionaries and flags only what both reject, which is the no-detector answer to a pair with no script boundary. The word card does the same in the other direction: "data" is a word in both languages, so Petal shows both readings rather than guessing which she meant. Writing the tests caught the one real bug — extendedAlphabet was a snapshot while correct/suggest read live, and her dictionary arrives after English, so every lookup would have resolved "cora" while the underlines were already right. Not done, and not claimed: the pack has not been read by a pt-PT speaker, and the Piper voice is deferred with the deploy. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
// The European Portuguese pack — the first pair that is not Chinese, and the
|
||||
// one that proves a language really is data.
|
||||
//
|
||||
// ⚠️ WRITTEN BUT NOT YET REVIEWED BY A NATIVE SPEAKER.
|
||||
// SUGGESTIONS.md §3 sets the bar: "the pt-PT pack should be reviewed by a pt-PT
|
||||
// speaker before it's trusted — same standard the zh copy got by being written
|
||||
// for a real reader." That review has not happened. Until it does, treat every
|
||||
// line here as a good-faith draft rather than as shipped copy, and expect a
|
||||
// speaker to change the register long before they change the vocabulary.
|
||||
//
|
||||
// European Portuguese, not Brazilian. That is the single most likely way for
|
||||
// this file to go quietly wrong, so the choices are deliberate throughout:
|
||||
//
|
||||
// * Post-Acordo spellings (ação, direção, ótimo), but the pt-PT lexicon —
|
||||
// ficheiro not arquivo, ecrã not tela, guardar not salvar, eliminar not
|
||||
// deletar, telemóvel not celular, sinónimo not sinônimo, académico not
|
||||
// acadêmico.
|
||||
// * "Estás a escrever", not "está escrevendo". The progressive with *a* +
|
||||
// infinitive is the European construction, and the gerund is the single
|
||||
// clearest tell of a Brazilian text.
|
||||
// * Second person singular *tu*, because Petal is a companion in someone's
|
||||
// private notebook and *você* would put a desk between them.
|
||||
// * "Iniciar sessão" / "Terminar sessão", not "fazer login" / "sair".
|
||||
//
|
||||
// Portuguese first, English underneath — same shape as the zh pack, for the
|
||||
// same reason: she reads her own language faster, and the English half is what
|
||||
// she is here to learn.
|
||||
|
||||
import type { Pack } from '../types'
|
||||
|
||||
export const ptPT: Pack = {
|
||||
code: 'pt-PT',
|
||||
nativeName: 'Português',
|
||||
|
||||
app: {
|
||||
duplicateTitle: (title) => `${title} (cópia)`,
|
||||
garden: 'Jardim de palavras',
|
||||
history: 'Histórico',
|
||||
},
|
||||
|
||||
auth: {
|
||||
title: 'Inicia sessão outra vez',
|
||||
titleEn: 'Please sign in again',
|
||||
bodyWithDraft:
|
||||
'O que acabaste de escrever está guardado neste dispositivo — volta a entrar e guarda-se sozinho.',
|
||||
bodyWithDraftEn: "What you just wrote is safe on this device — it'll save itself once you're back in.",
|
||||
bodyPlain: 'A tua sessão expirou. Tudo o que escreveste já está guardado.',
|
||||
bodyPlainEn: 'Your session expired. Everything you wrote is already saved.',
|
||||
signIn: 'Iniciar sessão · Sign in',
|
||||
},
|
||||
|
||||
companion: {
|
||||
choose: 'Escolhe um companheiro · Choose a companion',
|
||||
|
||||
encouragements: [
|
||||
{ native: 'Que bom! Esta frase ficou bem mais fluida 🌸', en: 'Lovely — that reads so much smoother now.' },
|
||||
{ native: 'Estás a escrever cada vez melhor ✨', en: "You're getting better and better." },
|
||||
{ native: 'Gosto muito desta mudança 💕', en: 'I really like that change.' },
|
||||
{ native: 'Continua assim — tu consegues!', en: 'Keep going — you’ve got this!' },
|
||||
{ native: 'Pois é, assim ficou muito mais claro 👍', en: 'Mm, that’s much clearer.' },
|
||||
{ native: 'Que boa escolha de palavra 🌷', en: 'That’s such a good word choice.' },
|
||||
{ native: 'Ui, este parágrafo lê-se tão bem ☁️', en: 'Ooh, that paragraph flows so nicely.' },
|
||||
{ native: 'Adoro ver-te escrever com mais confiança 💛', en: 'I love watching you write with more confidence.' },
|
||||
{ native: 'Cada bocadinho de progresso conta 🌱', en: 'Every little bit of progress counts.' },
|
||||
{ native: 'Hoje as tuas palavras estão a brilhar ✨', en: 'Your words are sparkling today.' },
|
||||
],
|
||||
|
||||
tips: [
|
||||
{ native: 'Dica: em inglês, frases mais curtas leem-se melhor.', en: 'Tip: shorter English sentences often read clearer.' },
|
||||
{ native: 'Não te esqueças dos artigos “the” e “a”.', en: "Don't forget articles like “the” and “a”." },
|
||||
{ native: 'Para o passado, usa o past tense: go → went.', en: 'For the past, use past tense: go → went.' },
|
||||
{ native: 'Ler em voz alta ajuda a apanhar o que soa estranho.', en: 'Reading aloud helps you catch awkward spots.' },
|
||||
{ native: 'Uma ideia por parágrafo e fica tudo arrumado.', en: 'One idea per paragraph keeps it tidy.' },
|
||||
{ native: 'Se tiveres dúvidas, pergunta-me ✨', en: 'Not sure about something? Just ask me. ✨' },
|
||||
{ native: 'O plural leva “s”: two apples 🍎', en: 'Plurals take an “s”: two apples 🍎' },
|
||||
// One tip the zh pack has no use for: the false friends between
|
||||
// Portuguese and English are a daily hazard for this writer and a
|
||||
// non-problem for the last one.
|
||||
{ native: 'Atenção aos falsos amigos: “pretender” não é *to pretend*.', en: 'Careful with false friends — “pretender” means to intend.' },
|
||||
],
|
||||
|
||||
breaks: [
|
||||
{ native: 'Já escreves há um bom bocado — levanta-te e descansa os olhos 🍵', en: "You've been writing a while — stretch and rest your eyes. 🍵" },
|
||||
{ native: 'Bebe um copo de água e para cinco minutos?', en: 'Sip some water and take five?' },
|
||||
{ native: 'Olha ao longe um momento, dá uma folga aos olhos 🌿', en: 'Look into the distance for a moment — give your eyes a break. 🌿' },
|
||||
],
|
||||
|
||||
// Late-night nudges. The English wit is the user's own and is kept word for
|
||||
// word across every pack; the Portuguese line leads gently into it, exactly
|
||||
// as the Mandarin one does.
|
||||
bedtime: [
|
||||
{ native: 'A tua cama deve estar com saudades 🛏️', en: 'I bet your bed is missing you right now.' },
|
||||
{ native: 'Cansada não se escreve bem — vai descansar 🌙', en: 'A tired writer is a bad writer — get some rest.' },
|
||||
{ native: 'Dorme sobre o assunto, que as ideias vêm sozinhas ✨', en: 'Sleep is a wondrous enabler.' },
|
||||
{ native: 'Ouves? Pois não… está toda a gente a dormir, e tu também devias 😴', en: "Hear that? No… you don't, because everyone is sleeping and you should be too." },
|
||||
// Portuguese proverbs on sleep and haste, in place of the Chinese ones —
|
||||
// a pack is not a translation of another pack.
|
||||
{ native: 'Deitar cedo e cedo erguer dá saúde e faz crescer.', en: 'Early to bed and early to rise makes you healthy and helps you grow.' },
|
||||
{ native: 'Quem dorme sobre o problema, acorda com a solução.', en: 'Sleep on the problem and you wake with the answer.' },
|
||||
{ native: 'Depressa e bem, há pouco quem.', en: 'Fast and good together — few manage that.' },
|
||||
],
|
||||
|
||||
greeting: { native: 'Olá! Estou aqui a fazer-te companhia 🐱', en: "Hi! I'm right here keeping you company. 🐱" },
|
||||
welcomeBack: { native: 'Bem-vinda de volta ✨ vamos continuar!', en: 'Welcome back ✨ let’s keep going!' },
|
||||
|
||||
errors: [
|
||||
{ native: 'Ups — houve um percalço, mas as tuas palavras estão a salvo.', en: 'Oops — a little hiccup, but your words are safe.' },
|
||||
{ native: 'Bolas, encravei por um segundo — já volto.', en: 'Haiya, I got stuck for a sec — back in a moment.' },
|
||||
{ native: 'Não te preocupes, tentamos outra vez daqui a pouco 🍵', en: "Don't worry — let's try again in a bit. 🍵" },
|
||||
],
|
||||
|
||||
milestone: (words: number) => ({
|
||||
native: `Uau! Já vais em ${words} palavras 🎉`,
|
||||
en: `Wow — ${words} words already! Amazing. 🎉`,
|
||||
}),
|
||||
|
||||
names: {
|
||||
cat: 'Gato dorminhoco',
|
||||
dog: 'Cão contente',
|
||||
'wiggle-dog': 'Cão abanão',
|
||||
butterfly: 'Borboleta',
|
||||
parrot: 'Papagaio',
|
||||
},
|
||||
},
|
||||
|
||||
prose: {
|
||||
longSentence: 'Esta frase está um bocadinho longa — dividi-la em duas ou três fica mais claro 🌸',
|
||||
commaSplice: 'Aqui há duas frases ligadas só por uma vírgula. Podes usar um ponto final, ou juntar “and / but”.',
|
||||
vagueThis: (word) => `Não se percebe bem a que “${word}” se refere — vale a pena dizê-lo (por exemplo, “${word} idea / change…”).`,
|
||||
oxfordComma: 'Numa lista de três ou mais, uma vírgula antes de “and / or” também ajuda a ler (a vírgula de Oxford).',
|
||||
transitionComma: (word) => `Depois de uma palavra de ligação no início, põe uma vírgula: “${word}, …”.`,
|
||||
capitalizeSentence: 'Começa cada frase com letra maiúscula.',
|
||||
repeatedWord: (word) => `“${word}” parece estar escrito duas vezes — dá uma vista de olhos.`,
|
||||
capitalizeI: 'Em inglês, o “I” (eu) escreve-se sempre com maiúscula.',
|
||||
spaceBeforePunct: 'Em inglês não se põe espaço antes da pontuação: a vírgula e o ponto vêm colados à palavra.',
|
||||
spaceAfterPunct: 'Depois da vírgula ou do ponto, deixa um espaço antes da palavra seguinte.',
|
||||
articleAn: (word) => `Antes de som de vogal usa-se “an”: “an ${word}”.`,
|
||||
articleA: (word) => `Antes de som de consoante usa-se “a”: “a ${word}”.`,
|
||||
uncountable: (word, singular) => `“${word}” é incontável em inglês — não leva s, basta “${singular}”.`,
|
||||
capitalizeProper: (fixed) => `Em inglês, línguas, nacionalidades, dias da semana e meses levam maiúscula: “${fixed}”.`,
|
||||
thirdPersonS: (subject, verb) => `Com he/she/it, o verbo leva -s: “${subject} ${verb}”.`,
|
||||
pluralAfter: (determiner, noun) => `Depois de “${determiner}” o nome vai no plural: “${determiner} ${noun}s”.`,
|
||||
doubleDeterminer: (first, second) => `“${first} ${second}” tem dois determinantes — fica só com um (por exemplo, tira “${first}”).`,
|
||||
thereArePlural: (noun) => `Com plural usa-se “there are”: “there are ${noun}…”.`,
|
||||
itsOwn: '“it’s” = “it is”. Para dizer “o seu / dele”, é “its” — portanto “its own”.',
|
||||
itsIs: (rest) => `Aqui é “it’s ${rest}” (it is); “its” é o possessivo.`,
|
||||
thanNotThen: (word) => `Nas comparações usa-se “than”, não “then”: “${word} than”.`,
|
||||
},
|
||||
|
||||
docs: {
|
||||
sortRecent: 'Recentes · Recent',
|
||||
sortTitle: 'Título · Title',
|
||||
sortLongest: 'Mais longos · Longest',
|
||||
backUpAll: 'Cópia de segurança · Back up all:',
|
||||
signOut: 'Terminar sessão · Sign out',
|
||||
duplicate: 'Duplicar · Duplicate',
|
||||
searchPlaceholder: 'Procurar · Search',
|
||||
searching: 'A procurar… · Searching…',
|
||||
noMatches: 'Sem resultados · No matches',
|
||||
tags: 'Etiquetas · Tags',
|
||||
newTagPlaceholder: 'Nova etiqueta · New tag',
|
||||
},
|
||||
|
||||
editor: {
|
||||
askPlaceholder: 'Ask why… / Pergunta porquê…',
|
||||
findPlaceholder: 'Localizar · Find',
|
||||
findNone: 'Nada · 0',
|
||||
matchCase: 'Match case · Maiúsculas/minúsculas',
|
||||
close: 'Close · Fechar',
|
||||
replacePlaceholder: 'Substituir por · Replace',
|
||||
replace: 'Substituir',
|
||||
replaceAll: 'Tudo',
|
||||
spelling: 'Ortografia · Spelling',
|
||||
noSuggestions: 'Sem sugestões · No suggestions',
|
||||
addToDictionary: 'Adicionar ao dicionário · Add to dictionary',
|
||||
readSelection: 'Ler a seleção em voz alta · Read selection aloud',
|
||||
rewrite: 'Reescrever · Rewrite',
|
||||
rewriting: 'A reescrever… · Rewriting…',
|
||||
rewriteFailed: 'Não deu para reescrever — tenta outra vez · Couldn’t rewrite',
|
||||
cancel: 'Cancelar · Cancel',
|
||||
retry: 'Tentar de novo · Retry',
|
||||
useThis: 'Usar esta · Use this',
|
||||
word: 'Palavra · Word',
|
||||
inGarden: 'Já está no jardim · In your garden (tap to remove)',
|
||||
saveToGarden: 'Guardar no jardim · Save to garden',
|
||||
readAloud: 'Ler em voz alta · Read aloud',
|
||||
lookingUp: 'A procurar… · Looking up…',
|
||||
definition: 'Definição · Definition',
|
||||
synonyms: 'Sinónimos · Synonyms',
|
||||
tapToSwap: 'toca para trocar · tap to swap',
|
||||
nothingFound: 'Não encontrei esta palavra · Nothing found for this word',
|
||||
origin: 'Origem · Origin',
|
||||
// This one the pt-PT pair actually sees: sale, comum, tarde, ali, data and
|
||||
// dozens more are words on both sides of the pair.
|
||||
alsoIn: 'Também é palavra em português · Also a word in Portuguese',
|
||||
wordBands: {
|
||||
simple: { native: 'Do dia a dia', en: 'Everyday word' },
|
||||
standard: { native: 'Normal', en: 'Standard' },
|
||||
advanced: { native: 'Avançada', en: 'Advanced' },
|
||||
},
|
||||
},
|
||||
|
||||
styles: {
|
||||
natural: { native: 'Mais natural', en: 'Natural' },
|
||||
academic: { native: 'Académico', en: 'Academic' },
|
||||
professional: { native: 'Profissional', en: 'Professional' },
|
||||
casual: { native: 'Descontraído', en: 'Casual' },
|
||||
humorous: { native: 'Bem-humorado', en: 'Humorous' },
|
||||
creative: { native: 'Criativo', en: 'Creative' },
|
||||
persuasive: { native: 'Persuasivo', en: 'Persuasive' },
|
||||
},
|
||||
|
||||
tones: {
|
||||
general: { native: 'Geral', en: 'General' },
|
||||
academic: { native: 'Académico', en: 'Academic' },
|
||||
professional: { native: 'Profissional', en: 'Professional' },
|
||||
casual: { native: 'Descontraído', en: 'Casual' },
|
||||
humorous: { native: 'Bem-humorado', en: 'Humorous' },
|
||||
creative: { native: 'Criativo', en: 'Creative' },
|
||||
persuasive: { native: 'Persuasivo', en: 'Persuasive' },
|
||||
},
|
||||
|
||||
exports: {
|
||||
label: 'Exportar',
|
||||
print: 'Imprimir / PDF',
|
||||
formats: {
|
||||
md: { native: 'Markdown', en: 'Markdown (.md)' },
|
||||
docx: { native: 'Documento Word', en: 'Word (.docx)' },
|
||||
html: { native: 'Página web', en: 'Web page (.html)' },
|
||||
txt: { native: 'Texto simples', en: 'Plain text (.txt)' },
|
||||
},
|
||||
},
|
||||
|
||||
garden: {
|
||||
title: 'Jardim de palavras · Vocabulary Garden',
|
||||
titleWithFlower: '🌷 Jardim de palavras · Vocabulary Garden',
|
||||
reviewing: 'A rever · Reviewing — recall, then grade yourself',
|
||||
subtitle: 'Words you looked up, blooming as you learn them',
|
||||
reviewDue: (n) => `Rever ${n} palavra${n === 1 ? '' : 's'} · Review ${n} due 🌸`,
|
||||
emptyLead: 'O teu jardim ainda está vazio.',
|
||||
emptyHint: 'Clica com o botão direito numa palavra inglesa para a procurar — e ela germina aqui.',
|
||||
due: 'a rever · due',
|
||||
seen: (reps, intervalDays) => `${reps}× revista · seen ${reps}× · intervalo ${intervalDays}d`,
|
||||
readAloud: '🔊 Ler',
|
||||
source: '📄 Origem · Source',
|
||||
remove: '🗑 Remover',
|
||||
growing: (n) => `🐱💤 ${n} flor${n === 1 ? '' : 'es'} no jardim · ${n} blossom${n > 1 ? 's' : ''} growing`,
|
||||
end: 'Terminar · End',
|
||||
promptProduction: 'Qual é a palavra inglesa? · Which English word?',
|
||||
promptRecognition: 'O que significa? · What does this mean?',
|
||||
showAnswer: 'Ver a resposta · Show answer',
|
||||
gradeAgain: { native: 'Outra vez', en: 'Again' },
|
||||
gradeGood: { native: 'Lembro-me', en: 'Good' },
|
||||
gradeEasy: { native: 'Fácil', en: 'Easy' },
|
||||
},
|
||||
|
||||
history: {
|
||||
title: 'Histórico · History',
|
||||
kinds: {
|
||||
manual: { native: 'Ponto guardado', en: 'Saved point' },
|
||||
auto: { native: 'Automático', en: 'Auto' },
|
||||
pre_restore: { native: 'Antes de restaurar', en: 'Before restore' },
|
||||
},
|
||||
justNow: 'just now · agora mesmo',
|
||||
minutesAgo: (n) => `${n} min ago · há ${n} min`,
|
||||
hoursAgo: (n) => `${n} hr ago · há ${n} h`,
|
||||
daysAgo: (n) => `${n} day${n > 1 ? 's' : ''} ago · há ${n} dia${n > 1 ? 's' : ''}`,
|
||||
preview: 'Pré-visualizar · Preview',
|
||||
restoring: 'Restoring…',
|
||||
restoreThis: 'Restaurar esta versão · Restore this version',
|
||||
passport: '📜 Certificado de escrita · Writing passport',
|
||||
keepFullHistory: 'Guardar o histórico completo · Keep full history',
|
||||
},
|
||||
|
||||
status: {
|
||||
savedLocally: 'Guardado neste dispositivo · Kept on this device',
|
||||
helperRestingNative: 'O ajudante está a descansar',
|
||||
helperRestingEn: "· Petal's helper is resting · o teu texto está guardado",
|
||||
soundsOn: 'Som ligado · Sounds on',
|
||||
soundsOff: 'Som desligado · Sounds off',
|
||||
petalsOn: 'Pétalas ligadas · Petals on',
|
||||
petalsOff: 'Pétalas desligadas · Petals off',
|
||||
statsTitle: 'Estatísticas · Writing stats',
|
||||
stats: {
|
||||
words: { native: 'Palavras', en: 'Words' },
|
||||
characters: { native: 'Caracteres', en: 'Characters' },
|
||||
sentences: { native: 'Frases', en: 'Sentences' },
|
||||
paragraphs: { native: 'Parágrafos', en: 'Paragraphs' },
|
||||
pages: { native: 'Páginas', en: 'Pages' },
|
||||
readingTime: { native: 'Tempo de leitura', en: 'Reading time' },
|
||||
avgWordLength: { native: 'Comprimento médio', en: 'Avg word length' },
|
||||
variety: { native: 'Variedade vocabular', en: 'Word variety' },
|
||||
readability: { native: 'Nível de leitura', en: 'Reading level' },
|
||||
},
|
||||
readability: {
|
||||
easy: { native: 'Fácil', en: 'Easy' },
|
||||
standard: { native: 'Normal', en: 'Standard' },
|
||||
fairlyHard: { native: 'Algo difícil', en: 'Fairly hard' },
|
||||
advanced: { native: 'Avançado', en: 'Advanced' },
|
||||
},
|
||||
},
|
||||
|
||||
toolbar: {
|
||||
untitledHeading: '(sem título)',
|
||||
outline: 'Estrutura · Outline',
|
||||
outlineHint: 'Usa H1/H2/H3 para criar títulos e a navegação aparece aqui.',
|
||||
},
|
||||
|
||||
update: {
|
||||
available: 'Há uma versão nova',
|
||||
refresh: 'Atualizar · Refresh',
|
||||
dismiss: 'Mais tarde · Dismiss',
|
||||
},
|
||||
}
|
||||
@@ -175,6 +175,10 @@ export const zh: Pack = {
|
||||
tapToSwap: '点击替换 · tap to swap',
|
||||
nothingFound: '没有找到这个词 · Nothing found for this word',
|
||||
origin: '词源 · Origin',
|
||||
// Never rendered for this pair — English and Chinese share no spellings, so
|
||||
// a word is never both. Written out anyway because the type demands it and
|
||||
// because "unreachable" is a claim about today's data, not a guarantee.
|
||||
alsoIn: '这个词在中文里也有 · Also a word in Chinese',
|
||||
wordBands: {
|
||||
simple: { native: '常用词', en: 'Everyday word' },
|
||||
standard: { native: '一般难度', en: 'Standard' },
|
||||
|
||||
Reference in New Issue
Block a user