i18n on the web - deepseek translations

This commit is contained in:
Priec
2026-08-14 20:35:37 +02:00
parent 2dc0d94ba0
commit 8f1bfdbe0c
91 changed files with 4679 additions and 1237 deletions

View File

@@ -25,6 +25,7 @@ use crate::{
AddTableColumnsRequest, CopyProfileRequest, CreateInvoiceTemplateTableRequest,
DeleteTableRequest, RenameColumnAliasRequest,
},
{i18n::Locale, tr},
schema::{ColumnForm, proto_columns},
services::{authenticated_request, reject_cross_site},
};
@@ -144,7 +145,7 @@ async fn show(state: AppState, headers: HeaderMap, inputs: PageInputs, page: Pag
loaded.active = page.name();
Html(page.render_page(&loaded)).into_response()
}
Err(error) => load_error_response(error),
Err(error) => load_error_response(&headers, error),
}
}
@@ -169,20 +170,21 @@ pub(crate) async fn update_columns(
let mut definitions = state.definitions.clone();
let catalog = match loader::load_column_catalog(&mut definitions, &headers).await {
Ok(catalog) => catalog,
Err(error) => return load_error_response(error),
Err(error) => return load_error_response(&headers, error),
};
let mut inputs = PageInputs::for_selection(selection);
inputs.columns = form.to_draft(catalog, false);
let index = form.index.unwrap_or(0);
let locale = Locale::from_headers(&headers);
match form.action.as_str() {
"add-column" => match inputs.columns.add_from_inputs() {
"add-column" => match inputs.columns.add_from_inputs(locale) {
Ok(status) => inputs.status = Some(status),
Err(message) => inputs.error = Some(message),
},
"remove-column" => {
if let Err(message) = inputs.columns.remove(index) {
if let Err(message) = inputs.columns.remove(locale, index) {
inputs.error = Some(message);
}
}
@@ -195,7 +197,7 @@ pub(crate) async fn update_columns(
loaded.active = Page::Columns.name();
Html(ui::render_column_panel(&loaded)).into_response()
}
Err(error) => load_error_response(error),
Err(error) => load_error_response(&headers, error),
}
}
@@ -215,29 +217,43 @@ pub(crate) async fn add_columns(
let mut definitions = state.definitions.clone();
let catalog = match loader::load_column_catalog(&mut definitions, &headers).await {
Ok(catalog) => catalog,
Err(error) => return load_error_response(error),
Err(error) => return load_error_response(&headers, error),
};
let mut inputs = PageInputs::for_selection(selection);
inputs.columns = form.to_draft(catalog.clone(), false);
if !inputs.selection.has_table() {
return refuse(state, headers, inputs, Page::Columns, "Select a table first.".to_string())
.await;
}
if inputs.columns.is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-select-table-first"
);
return refuse(
state,
headers,
inputs,
Page::Columns,
"Describe at least one column before adding.".to_string(),
message,
)
.await;
}
if inputs.columns.is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-describe-column"
);
return refuse(
state,
headers,
inputs,
Page::Columns,
message,
)
.await;
}
// The same checks the server runs, applied to a draft that may have been
// rebuilt from a posted form rather than through the panel.
if let Err(message) = inputs.columns.validate() {
if let Err(message) = inputs.columns.validate(Locale::from_headers(&headers)) {
return refuse(state, headers, inputs, Page::Columns, message).await;
}
@@ -259,10 +275,11 @@ pub(crate) async fn add_columns(
Ok(response) if response.get_ref().success => {
let added = inputs.columns.added.len();
inputs.sql = Some(response.into_inner().sql);
inputs.status = Some(format!(
"{added} column{} added to `{}`.",
if added == 1 { "" } else { "s" },
inputs.selection.table
inputs.status = Some(tr!(
Locale::from_headers(&headers),
"td-columns-added",
"count" => added as i64,
"table" => inputs.selection.table.clone(),
));
// The columns are the table's now, so the panel starts empty.
inputs.columns = crate::schema::ColumnDraft::for_append(catalog);
@@ -271,7 +288,10 @@ pub(crate) async fn add_columns(
Ok(response) => {
let message = response.into_inner().sql;
let message = if message.is_empty() {
"The backend did not add the columns.".to_string()
tr!(
Locale::from_headers(&headers),
"td-err-backend-no-columns"
)
} else {
message
};
@@ -300,12 +320,16 @@ pub(crate) async fn rename_column(
inputs.rename = form.clone();
if form.old_column_name.is_empty() || form.new_column_name.trim().is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-choose-rename"
);
return refuse(
state,
headers,
inputs,
Page::Columns,
"Choose a column and type its new name.".to_string(),
message,
)
.await;
}
@@ -334,7 +358,10 @@ pub(crate) async fn rename_column(
Ok(response) => {
let message = response.into_inner().message;
let message = if message.is_empty() {
"The backend did not rename the column.".to_string()
tr!(
Locale::from_headers(&headers),
"td-err-backend-no-rename"
)
} else {
message
};
@@ -367,12 +394,16 @@ pub(crate) async fn delete_table(
// The typed name is the whole guard: the drop is CASCADE, and it takes the
// profile with it when this was its last table.
if form.confirm_table_name.trim() != form.table || form.table.is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-type-name"
);
return refuse(
state,
headers,
inputs,
Page::Delete,
"Type the table's name exactly to confirm the deletion.".to_string(),
message,
)
.await;
}
@@ -404,7 +435,10 @@ pub(crate) async fn delete_table(
Ok(response) => {
let message = response.into_inner().message;
let message = if message.is_empty() {
"The backend did not delete the table.".to_string()
tr!(
Locale::from_headers(&headers),
"td-err-backend-no-delete"
)
} else {
message
};
@@ -433,12 +467,16 @@ pub(crate) async fn copy_profile(
inputs.copy = form.clone();
if form.target_profile_name.trim().is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-name-profile"
);
return refuse(
state,
headers,
inputs,
Page::Copy,
"Name the profile to copy into.".to_string(),
message,
)
.await;
}
@@ -456,9 +494,12 @@ pub(crate) async fn copy_profile(
match definitions.copy_profile(request).await {
Ok(response) if response.get_ref().success => {
let response = response.into_inner();
inputs.status = Some(format!(
"{}{} table(s) and {} script(s) copied.",
response.message, response.tables_copied, response.scripts_copied
inputs.status = Some(tr!(
Locale::from_headers(&headers),
"td-profile-copied",
"message" => response.message.clone(),
"tables" => response.tables_copied as i64,
"scripts" => response.scripts_copied as i64,
));
inputs.copy = CopyForm {
profile: form.profile,
@@ -469,7 +510,10 @@ pub(crate) async fn copy_profile(
Ok(response) => {
let message = response.into_inner().message;
let message = if message.is_empty() {
"The backend did not copy the profile.".to_string()
tr!(
Locale::from_headers(&headers),
"td-err-backend-no-copy"
)
} else {
message
};
@@ -496,12 +540,16 @@ pub(crate) async fn create_from_invoice_template(
inputs.invoice = form.clone();
if form.table_name.trim().is_empty() || form.typst_source.trim().is_empty() {
let message = tr!(
Locale::from_headers(&headers),
"td-err-template-required"
);
return refuse(
state,
headers,
inputs,
Page::Template,
"A table name and the template's source are both required.".to_string(),
message,
)
.await;
}
@@ -520,9 +568,10 @@ pub(crate) async fn create_from_invoice_template(
match definitions.create_invoice_template_table(request).await {
Ok(response) if response.get_ref().success => {
let response = response.into_inner();
inputs.status = Some(format!(
"{} table(s) created from the template.",
response.tables.len()
inputs.status = Some(tr!(
Locale::from_headers(&headers),
"td-template-tables-created",
"count" => response.tables.len() as i64,
));
inputs.generated = response
.tables
@@ -540,12 +589,16 @@ pub(crate) async fn create_from_invoice_template(
respond(state, headers, inputs, Page::Template, StatusCode::OK).await
}
Ok(_) => {
let message = tr!(
Locale::from_headers(&headers),
"td-err-backend-no-template"
);
refuse(
state,
headers,
inputs,
Page::Template,
"The backend did not create the template's tables.".to_string(),
message,
)
.await
}
@@ -568,7 +621,7 @@ async fn respond(
loaded.active = page.name();
(status, Html(page.render_fragment(&loaded))).into_response()
}
Err(error) => load_error_response(error),
Err(error) => load_error_response(&headers, error),
}
}
@@ -584,19 +637,23 @@ async fn refuse(
respond(state, headers, inputs, page, StatusCode::UNPROCESSABLE_ENTITY).await
}
fn load_error_response(error: LoadError) -> Response {
fn load_error_response(headers: &HeaderMap, error: LoadError) -> Response {
match error {
LoadError::Unauthenticated => Redirect::to("/login").into_response(),
LoadError::Forbidden => (
StatusCode::FORBIDDEN,
Html(ui::render_load_error(
"Table-management permission is required.",
Locale::from_headers(headers),
&tr!(
Locale::from_headers(headers),
"td-err-permission"
),
)),
)
.into_response(),
LoadError::Backend(message) => (
StatusCode::BAD_GATEWAY,
Html(ui::render_load_error(&message)),
Html(ui::render_load_error(Locale::from_headers(headers), &message)),
)
.into_response(),
}