Translate the labels the extractor could not see
Three places built user-visible English out of expressions rather than
writing it as JSX text, so the extraction codemod never found them and
they stayed English in all nine languages — including the five that have
been in production for weeks.
The calendar view switcher was the worst of them: it spelled its labels
as `v[0].toUpperCase() + v.slice(1)`, which is correct English and
untranslatable anywhere else. Day, Week, Month and Agenda were already in
every catalogue, sitting unused, because the buttons never asked for
them. They now come from a Record<View, () => string>, so TypeScript
makes the map exhaustive and adding a view forces adding its label. The
labels are functions rather than values: a module-level object would
capture whichever language happened to load first and keep it.
The other two needed new source strings, added to all nine catalogues:
the composer's title for an untitled draft ("New message"), its status
line ("Sending…", "Saving…", "Error", "Saved {when}", "Unsaved"), and the
agenda view's own title ("Agenda from {date}").
This is not the whole of it. A sweep for the same shape — template
literals, toast arguments, and dialog props rather than JSX text — turns
up roughly a hundred more strings, mostly toasts and confirmation
dialogs. Those are a second extraction pass rather than a fix, and are
left for one.
This commit is contained in:
@@ -875,6 +875,15 @@ export const catalog: Catalog = {
|
||||
"Delete “{name}”?": "„{name}“ löschen?",
|
||||
"Rename folder": "Ordner umbenennen",
|
||||
"Search: {query}": "Suche: {query}",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Neue Nachricht",
|
||||
"Sending…": "Wird gesendet…",
|
||||
"Saving…": "Wird gespeichert…",
|
||||
"Error": "Fehler",
|
||||
"Saved {when}": "Gespeichert {when}",
|
||||
"Unsaved": "Nicht gespeichert",
|
||||
"Agenda from {date}": "Agenda ab {date}",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} Nachricht", other: "{n} Nachrichten" },
|
||||
|
||||
@@ -848,6 +848,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Esta traducción la ha generado una IA y no la ha revisado ninguna persona de habla nativa, así que está marcada como Beta hasta que alguien la dé por buena. Todo lo que suene mal merece un aviso: {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} es la paleta de {site}, y con la que empieza una cuenta nueva. Es un tema oscuro, así que cuenta como oscuro allí donde importa, y el color de acento de abajo se sigue aplicando encima.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "La versión de ihasmail es la fecha del commit a partir del cual se compiló, seguida de su procedencia: {example} se compiló a partir de un commit del 30 de agosto de 2026 que llegó mediante la pull request 129. Un commit que no llegó por esa vía lleva en su lugar su SHA corto: {sha}. La versión no dice nada sobre Stalwart a propósito; lo que esta compilación necesita del servidor está en la línea de arriba.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Mensaje nuevo",
|
||||
"Sending…": "Enviando…",
|
||||
"Saving…": "Guardando…",
|
||||
"Error": "Error",
|
||||
"Saved {when}": "Guardado {when}",
|
||||
"Unsaved": "Sin guardar",
|
||||
"Agenda from {date}": "Agenda desde {date}",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} mensaje", other: "{n} mensajes" },
|
||||
|
||||
@@ -853,6 +853,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Cette traduction a été générée par une IA et n'a pas été relue par une personne de langue maternelle française ; elle est donc marquée Beta jusqu'à validation. Tout ce qui sonne faux mérite d'être signalé — {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} est la palette de {site}, et celle d'un nouveau compte. C'est un thème sombre : il compte donc comme sombre partout où cela importe, et la couleur d'accentuation ci-dessous s'y applique toujours.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "La version de ihasmail est la date du commit à partir duquel elle a été construite, suivie de l'origine de ce commit : {example} provient d'un commit daté du 30 août 2026 arrivé via la pull request 129. Un commit qui n'est pas passé par là porte à la place son SHA court — {sha}. La version ne dit délibérément rien de Stalwart ; ce dont cette build a besoin du serveur figure à la ligne ci-dessus.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Nouveau message",
|
||||
"Sending…": "Envoi…",
|
||||
"Saving…": "Enregistrement…",
|
||||
"Error": "Erreur",
|
||||
"Saved {when}": "Enregistré {when}",
|
||||
"Unsaved": "Non enregistré",
|
||||
"Agenda from {date}": "Agenda à partir du {date}",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} message", other: "{n} messages" },
|
||||
|
||||
@@ -856,6 +856,15 @@ export const catalog: Catalog = {
|
||||
"Delete “{name}”?": "「{name}」を削除しますか?",
|
||||
"Rename folder": "フォルダー名を変更",
|
||||
"Search: {query}": "検索: {query}",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "新規メール",
|
||||
"Sending…": "送信中…",
|
||||
"Saving…": "保存中…",
|
||||
"Error": "エラー",
|
||||
"Saved {when}": "{when} に保存",
|
||||
"Unsaved": "未保存",
|
||||
"Agenda from {date}": "{date} からの予定",
|
||||
},
|
||||
plurals: {
|
||||
/*
|
||||
|
||||
@@ -844,6 +844,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Deze vertaling is door AI gemaakt en niet gecontroleerd door iemand met Nederlands als moedertaal; ze is daarom als Beta gemarkeerd tot iemand haar goedkeurt. Alles wat verkeerd klinkt, is een melding waard — {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} is het kleurenpalet van {site}, en waarmee een nieuw account begint. Het is een donker thema en telt dus overal als donker waar dat uitmaakt; de accentkleur hieronder werkt er nog steeds bovenop.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "De eigen versie van ihasmail is de datum van de commit waaruit het is gebouwd, gevolgd door waar die commit vandaan kwam: {example} is gebouwd uit een commit van 30 augustus 2026 die via pull request 129 binnenkwam. Een commit die niet via zo'n verzoek kwam, draagt in plaats daarvan zijn korte SHA — {sha}. De versie zegt bewust niets over Stalwart; wat deze build van de server nodig heeft, staat op de regel hierboven.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Nieuw bericht",
|
||||
"Sending…": "Bezig met verzenden…",
|
||||
"Saving…": "Bezig met opslaan…",
|
||||
"Error": "Fout",
|
||||
"Saved {when}": "Opgeslagen {when}",
|
||||
"Unsaved": "Niet opgeslagen",
|
||||
"Agenda from {date}": "Agenda vanaf {date}",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} bericht", other: "{n} berichten" },
|
||||
|
||||
@@ -851,6 +851,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Esta tradução foi gerada por IA e não foi revisada por uma pessoa nativa, então está marcada como Beta até que alguém a aprove. Tudo o que soar errado vale um aviso — {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} é a paleta de {site}, e com a qual uma conta nova começa. É um tema escuro, então conta como escuro onde isso importa, e a cor de destaque abaixo continua valendo por cima dele.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "A versão do próprio ihasmail é a data do commit a partir do qual ele foi compilado, seguida da origem desse commit: {example} foi compilado a partir de um commit de 30 de agosto de 2026 que veio pela pull request 129. Um commit que não veio por uma delas carrega no lugar o SHA curto — {sha}. A versão não diz nada sobre o Stalwart de propósito; o que esta compilação precisa do servidor está na linha acima.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Nova mensagem",
|
||||
"Sending…": "Enviando…",
|
||||
"Saving…": "Salvando…",
|
||||
"Error": "Erro",
|
||||
"Saved {when}": "Salvo {when}",
|
||||
"Unsaved": "Não salvo",
|
||||
"Agenda from {date}": "Agenda a partir de {date}",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} mensagem", other: "{n} mensagens" },
|
||||
|
||||
@@ -850,6 +850,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Этот перевод сделан ИИ и не проверен носителем языка, поэтому помечен как Beta до тех пор, пока кто-нибудь его не подтвердит. Обо всём, что звучит неправильно, стоит сообщить — {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} — это палитра с {site}, с которой начинает новая учётная запись. Тема тёмная, поэтому везде, где это важно, считается тёмной, а акцентный цвет ниже применяется поверх неё.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "Собственная версия ihasmail — это дата коммита, из которого он собран, и указание, откуда этот коммит взялся: {example} собран из коммита от 30 августа 2026 года, пришедшего через pull request 129. Коммит, пришедший иначе, несёт вместо этого короткий SHA — {sha}. Версия намеренно ничего не сообщает о Stalwart; то, что этой сборке нужно от сервера, указано строкой выше.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Новое письмо",
|
||||
"Sending…": "Отправка…",
|
||||
"Saving…": "Сохранение…",
|
||||
"Error": "Ошибка",
|
||||
"Saved {when}": "Сохранено {when}",
|
||||
"Unsaved": "Не сохранено",
|
||||
"Agenda from {date}": "Расписание с {date}",
|
||||
},
|
||||
plurals: {
|
||||
/*
|
||||
|
||||
@@ -844,6 +844,15 @@ export const catalog: Catalog = {
|
||||
"This translation was generated by AI and has not been checked by a native speaker, so it is marked Beta until somebody who speaks it signs it off. Anything that reads wrongly is worth reporting — {report}.": "Цей переклад зроблено ШІ й не перевірено носієм мови, тому його позначено як Beta, доки хтось його не підтвердить. Про все, що звучить неправильно, варто повідомити — {report}.",
|
||||
"{name} is the palette from {site}, and what a new account starts on. It is a dark theme, so it counts as dark wherever that matters, and the accent colour below still applies on top of it.": "{name} — це палітра з {site}, з якою починає новий обліковий запис. Тема темна, тому скрізь, де це важливо, вважається темною, а акцентний колір нижче застосовується поверх неї.",
|
||||
"ihasmail's own version is the date of the commit it was built from, followed by where that commit came from: {example} was built from a commit dated the 30th of August 2026 that arrived through pull request 129. A commit that did not come through one carries its short SHA instead — {sha}. The version deliberately says nothing about Stalwart; what this build needs from the server is the line above.": "Власна версія ihasmail — це дата коміту, з якого його зібрано, і вказівка, звідки цей коміт узявся: {example} зібрано з коміту від 30 серпня 2026 року, що надійшов через pull request 129. Коміт, який надійшов інакше, несе замість цього короткий SHA — {sha}. Версія навмисно нічого не повідомляє про Stalwart; те, що цій збірці потрібно від сервера, вказано рядком вище.",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "Новий лист",
|
||||
"Sending…": "Надсилання…",
|
||||
"Saving…": "Збереження…",
|
||||
"Error": "Помилка",
|
||||
"Saved {when}": "Збережено {when}",
|
||||
"Unsaved": "Не збережено",
|
||||
"Agenda from {date}": "Розклад від {date}",
|
||||
},
|
||||
plurals: {
|
||||
/*
|
||||
|
||||
@@ -855,6 +855,15 @@ export const catalog: Catalog = {
|
||||
"Delete “{name}”?": "删除「{name}」?",
|
||||
"Rename folder": "重命名文件夹",
|
||||
"Search: {query}": "搜索:{query}",
|
||||
|
||||
// ── Composer status, calendar title ────────────────────────────────
|
||||
"New message": "新邮件",
|
||||
"Sending…": "正在发送…",
|
||||
"Saving…": "正在保存…",
|
||||
"Error": "错误",
|
||||
"Saved {when}": "已保存 {when}",
|
||||
"Unsaved": "未保存",
|
||||
"Agenda from {date}": "从 {date} 起的日程",
|
||||
},
|
||||
plurals: {
|
||||
/*
|
||||
|
||||
@@ -15,6 +15,24 @@ import { CalendarContextMenu, eventColor, type CalendarContext } from "./Calenda
|
||||
import { t as translate } from "@/lib/i18n";
|
||||
|
||||
type View = "month" | "week" | "day" | "agenda";
|
||||
|
||||
/**
|
||||
* The switcher labels, spelled out rather than derived from the view id.
|
||||
*
|
||||
* They used to be `v[0].toUpperCase() + v.slice(1)`, which is correct English
|
||||
* and untranslatable in every other language: the extractor cannot see a
|
||||
* string that is computed, so the four buttons stayed English even in a
|
||||
* catalogue that had all four words. Called rather than looked up, because a
|
||||
* module-level object would capture the labels for whichever language loaded
|
||||
* first.
|
||||
*/
|
||||
const VIEW_LABELS: Record<View, () => string> = {
|
||||
day: () => translate("Day"),
|
||||
week: () => translate("Week"),
|
||||
month: () => translate("Month"),
|
||||
agenda: () => translate("Agenda"),
|
||||
};
|
||||
|
||||
const HOUR_H = 48;
|
||||
|
||||
export function CalendarView({ view: viewParam, date }: { view?: string; date?: string }) {
|
||||
@@ -99,7 +117,7 @@ export function CalendarView({ view: viewParam, date }: { view?: string; date?:
|
||||
effectiveView === "month" ? formatMonthYear(anchor)
|
||||
: effectiveView === "week" ? `${formatDayMonth(range.start)} – ${formatDate(addDays(range.end, -1))}`
|
||||
: effectiveView === "day" ? formatWeekdayDate(anchor, true)
|
||||
: `Agenda from ${formatDayMonth(anchor)}`;
|
||||
: translate("Agenda from {date}", { date: formatDayMonth(anchor) });
|
||||
|
||||
const onEvent = (inst: EventInstance, el: Element) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
@@ -127,7 +145,7 @@ export function CalendarView({ view: viewParam, date }: { view?: string; date?:
|
||||
{cal.loading && <span className="spinner" />}
|
||||
<div className="view-switch">
|
||||
{(["day", "week", "month", "agenda"] as View[]).filter((v) => !(isMobile && v === "week")).map((v) => (
|
||||
<button key={v} className={effectiveView === v ? "active" : ""} onClick={() => go(v, anchor)}>{v[0]!.toUpperCase() + v.slice(1)}</button>
|
||||
<button key={v} className={effectiveView === v ? "active" : ""} onClick={() => go(v, anchor)}>{VIEW_LABELS[v]()}</button>
|
||||
))}
|
||||
</div>
|
||||
{!isMobile && <button className="btn btn-primary btn-sm" onClick={() => openNew()}><Plus size={16} /> {translate("Event")}</button>}
|
||||
|
||||
@@ -141,8 +141,14 @@ export function Composer({ draft }: { draft: Draft }) {
|
||||
[allIdentities, hiddenIdentities, d.identityId, defaultIdentityId],
|
||||
);
|
||||
const ident = identities.find((i) => i.id === d.identityId) ?? identities[0];
|
||||
const title = d.subject || (d.replyMode ? (d.replyMode === "forward" ? "Forward" : "Reply") : "New message");
|
||||
const status = d.sending ? "Sending…" : d.saving ? "Saving…" : d.error ? "Error" : d.savedAt ? `Saved ${formatRelative(new Date(d.savedAt).toISOString())}` : d.dirty ? "Unsaved" : "";
|
||||
const title = d.subject || (d.replyMode ? (d.replyMode === "forward" ? translate("Forward") : translate("Reply")) : translate("New message"));
|
||||
const status =
|
||||
d.sending ? translate("Sending…")
|
||||
: d.saving ? translate("Saving…")
|
||||
: d.error ? translate("Error")
|
||||
: d.savedAt ? translate("Saved {when}", { when: formatRelative(new Date(d.savedAt).toISOString()) })
|
||||
: d.dirty ? translate("Unsaved")
|
||||
: "";
|
||||
const totalSize = d.attachments.reduce((n, a) => n + a.size, 0);
|
||||
|
||||
if (d.minimized) {
|
||||
|
||||
Reference in New Issue
Block a user