diff --git a/web/locales/cs/main.ftl b/web/locales/cs/main.ftl
index de6bd1c2..e01b0112 100644
--- a/web/locales/cs/main.ftl
+++ b/web/locales/cs/main.ftl
@@ -15,6 +15,7 @@ ui-back-to-admin-panel = ← Administrační panel
ui-back-to-form = Zpět do formuláře
ui-dismiss-alert = zavřít upozornění
notice-log-in-first = Nejprve se prosím přihlaste.
+services-cross-site-rejected = Zamítnuto odeslání formuláře z jiné stránky
nav-admin = Administrace
nav-permissions = Oprávnění
@@ -112,6 +113,10 @@ admin-unavailable-title = Administrační panel není dostupný
admin-forbidden = Tento účet nemá oprávnění otevřít administrační panel.
admin-profiles = Profily
+admin-global-label = Globální
+admin-err-unknown-profile = Neznámý profil '{ $name }'
+admin-err-table-not-in-profile = Tabulka '{ $name }' není součástí vybraného profilu
+admin-err-backend-no-structure = Backend nevrátil strukturu pro '{ $name }'
admin-no-profiles-available = Nejsou k dispozici žádné profily.
admin-table-count = { $count ->
[one] { $count } tabulka
diff --git a/web/locales/en/main.ftl b/web/locales/en/main.ftl
index 73eef2ae..718f2215 100644
--- a/web/locales/en/main.ftl
+++ b/web/locales/en/main.ftl
@@ -20,6 +20,7 @@ ui-back-to-admin-panel = ← Admin panel
ui-back-to-form = Back to the form
ui-dismiss-alert = dismiss alert
notice-log-in-first = Please log in first.
+services-cross-site-rejected = Cross-site form submission rejected
nav-admin = Admin
nav-permissions = Permissions
@@ -117,6 +118,10 @@ admin-unavailable-title = Admin panel unavailable
admin-forbidden = This account is not allowed to open the admin panel.
admin-profiles = Profiles
+admin-global-label = Global
+admin-err-unknown-profile = Unknown profile '{ $name }'
+admin-err-table-not-in-profile = Table '{ $name }' is not part of the selected profile
+admin-err-backend-no-structure = The backend did not return the structure for '{ $name }'
admin-no-profiles-available = No profiles available.
admin-table-count = { $count ->
[one] { $count } table
diff --git a/web/locales/sk/main.ftl b/web/locales/sk/main.ftl
index f82b3e41..eb63eb18 100644
--- a/web/locales/sk/main.ftl
+++ b/web/locales/sk/main.ftl
@@ -15,6 +15,7 @@ ui-back-to-admin-panel = ← Administračný panel
ui-back-to-form = Späť do formulára
ui-dismiss-alert = zatvoriť upozornenie
notice-log-in-first = Najprv sa prosím prihláste.
+services-cross-site-rejected = Zamietnuté odoslanie formulára z inej stránky
nav-admin = Administrácia
nav-permissions = Oprávnenia
@@ -112,6 +113,10 @@ admin-unavailable-title = Administračný panel nie je dostupný
admin-forbidden = Tento účet nemá povolenie otvoriť administračný panel.
admin-profiles = Profily
+admin-global-label = Globálne
+admin-err-unknown-profile = Neznámy profil '{ $name }'
+admin-err-table-not-in-profile = Tabuľka '{ $name }' nie je súčasťou vybraného profilu
+admin-err-backend-no-structure = Backend nevrátil štruktúru pre '{ $name }'
admin-no-profiles-available = Nie sú k dispozícii žiadne profily.
admin-table-count = { $count ->
[one] { $count } tabuľka
diff --git a/web/src/pages/add_logic/logic.rs b/web/src/pages/add_logic/logic.rs
index 5f0007b3..334f1966 100644
--- a/web/src/pages/add_logic/logic.rs
+++ b/web/src/pages/add_logic/logic.rs
@@ -37,7 +37,14 @@ pub(crate) async fn create_logic(
.get("sec-fetch-site")
.is_some_and(|value| value == "cross-site")
{
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
let request = match form.into_request(Locale::from_headers(&headers)) {
Ok(request) => request,
diff --git a/web/src/pages/add_table/draft.rs b/web/src/pages/add_table/draft.rs
index 7a58d142..e21f2027 100644
--- a/web/src/pages/add_table/draft.rs
+++ b/web/src/pages/add_table/draft.rs
@@ -1089,6 +1089,6 @@ mod tests {
source: "name".to_string(),
alias: "row_revision".to_string(),
}];
- assert!(draft.validate_generated_aliases().is_err());
+ assert!(draft.validate_generated_aliases(crate::i18n::Locale::default()).is_err());
}
}
diff --git a/web/src/pages/add_table/ui.rs b/web/src/pages/add_table/ui.rs
index cce9c359..b61c1128 100644
--- a/web/src/pages/add_table/ui.rs
+++ b/web/src/pages/add_table/ui.rs
@@ -512,7 +512,7 @@ mod tests {
/// A load failure has no draft to render, so the dialog is the response.
#[test]
fn a_load_failure_answers_with_the_dialog() {
- let html = render_submission_error("The backend is unreachable.");
+ let html = render_submission_error(crate::i18n::Locale::default(), "The backend is unreachable.");
assert!(!html.contains("Template error"), "{html}");
assert!(html.contains(r#"role="dialog""#));
diff --git a/web/src/pages/add_validation/logic.rs b/web/src/pages/add_validation/logic.rs
index dfd21e04..5a4c1358 100644
--- a/web/src/pages/add_validation/logic.rs
+++ b/web/src/pages/add_validation/logic.rs
@@ -37,7 +37,14 @@ pub(crate) async fn save_field_validation(
return render_loaded(&headers, Err(error));
}
if cross_site(&headers) {
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
if !form.validation_set_name.trim().is_empty() {
let request = match form.apply_set_request(Locale::from_headers(&headers)) {
@@ -125,7 +132,14 @@ pub(crate) async fn save_validation_rule(
return render_loaded(&headers, Err(error));
}
if cross_site(&headers) {
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
let request = match form.rule_request(Locale::from_headers(&headers)) {
Ok(request) => request,
@@ -174,7 +188,14 @@ pub(crate) async fn save_validation_set(
return render_set_loaded(&headers, Err(error));
}
if cross_site(&headers) {
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
let request = match form.request(Locale::from_headers(&headers)) {
Ok(request) => request,
diff --git a/web/src/pages/admin/admin/loader.rs b/web/src/pages/admin/admin/loader.rs
index faccba97..74cd35c7 100644
--- a/web/src/pages/admin/admin/loader.rs
+++ b/web/src/pages/admin/admin/loader.rs
@@ -61,7 +61,10 @@ pub(crate) async fn load_admin_page(
);
let mut profiles = vec![ProfileView {
- label: "Global".to_string(),
+ label: crate::tr!(
+ crate::i18n::Locale::from_headers(headers),
+ "admin-global-label"
+ ),
scope: GLOBAL_SCOPE.to_string(),
table_count: global_tables.len(),
global: true,
@@ -78,7 +81,11 @@ pub(crate) async fn load_admin_page(
Some(GLOBAL_SCOPE) => Some(global_tables),
Some(name) => {
if !profile_tree.profiles.iter().any(|profile| profile.name == name) {
- return Err(LoadError::InvalidSelection(format!("Unknown profile '{name}'")));
+ return Err(LoadError::InvalidSelection(crate::tr!(
+ crate::i18n::Locale::from_headers(headers),
+ "admin-err-unknown-profile",
+ "name" => name.to_string(),
+ )));
}
Some(table_scope::profile_owned_tables(&profile_tree.profiles, name))
}
@@ -112,8 +119,10 @@ pub(crate) async fn load_admin_page(
if let Some(table_name) = selected_table.as_deref()
&& (!scope_is_chosen || !tables.iter().any(|table| table.name == table_name))
{
- return Err(LoadError::InvalidSelection(format!(
- "Table '{table_name}' is not part of the selected profile"
+ return Err(LoadError::InvalidSelection(crate::tr!(
+ crate::i18n::Locale::from_headers(headers),
+ "admin-err-table-not-in-profile",
+ "name" => table_name.to_string(),
)));
}
@@ -135,8 +144,10 @@ pub(crate) async fn load_admin_page(
.table_structures
.get(table_name)
.ok_or_else(|| {
- LoadError::Backend(format!(
- "The backend did not return the structure for '{table_name}'"
+ LoadError::Backend(crate::tr!(
+ crate::i18n::Locale::from_headers(headers),
+ "admin-err-backend-no-structure",
+ "name" => table_name.to_string(),
))
})?
.columns
diff --git a/web/src/pages/admin/table_definition/state.rs b/web/src/pages/admin/table_definition/state.rs
index d71f3c1a..96920668 100644
--- a/web/src/pages/admin/table_definition/state.rs
+++ b/web/src/pages/admin/table_definition/state.rs
@@ -431,11 +431,11 @@ mod tests {
assert_eq!(renameable[0].name, "work_phone");
assert_eq!(renameable[1].name, "charge");
assert_eq!(
- detail.columns[1].flags(),
+ detail.columns[1].flags(&crate::i18n::Locale::default()),
vec!["read only", "generated from work_phone"]
);
assert_eq!(
- detail.columns[2].flags(),
+ detail.columns[2].flags(&crate::i18n::Locale::default()),
vec!["EUR", "generated from accounting"]
);
}
diff --git a/web/src/pages/admin/table_definition/ui.rs b/web/src/pages/admin/table_definition/ui.rs
index 89dd2d30..97f4cbe4 100644
--- a/web/src/pages/admin/table_definition/ui.rs
+++ b/web/src/pages/admin/table_definition/ui.rs
@@ -591,7 +591,7 @@ mod tests {
#[test]
fn a_load_failure_answers_with_the_dialog() {
- let html = render_load_error("The backend is unreachable.");
+ let html = render_load_error(crate::i18n::Locale::default(), "The backend is unreachable.");
assert!(!html.contains("Template error"), "{html}");
assert!(html.contains(r#"role="dialog""#));
diff --git a/web/src/pages/import_export/common/csv.rs b/web/src/pages/import_export/common/csv.rs
index 49bd60d7..663fa4ae 100644
--- a/web/src/pages/import_export/common/csv.rs
+++ b/web/src/pages/import_export/common/csv.rs
@@ -72,6 +72,6 @@ mod tests {
let fields = vec!["name".to_string(), "hello, \"world\"\nnext".to_string()];
let mut csv = String::new();
write_record(&mut csv, &fields);
- assert_eq!(parse_csv(&csv).unwrap(), vec![fields]);
+ assert_eq!(parse_csv(crate::i18n::Locale::default(), &csv).unwrap(), vec![fields]);
}
}
diff --git a/web/src/pages/import_export/export/logic.rs b/web/src/pages/import_export/export/logic.rs
index dc51ff87..f82a3ec0 100644
--- a/web/src/pages/import_export/export/logic.rs
+++ b/web/src/pages/import_export/export/logic.rs
@@ -49,7 +49,14 @@ pub(crate) async fn export_csv(
Err(error) => return load_error(&headers, error),
};
if cross_site(&headers) {
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
let (profile_name, table_names) = match form.targets(Locale::from_headers(&headers)) {
Ok(targets) => targets,
diff --git a/web/src/pages/import_export/import/logic.rs b/web/src/pages/import_export/import/logic.rs
index e9919c41..79f1c3fe 100644
--- a/web/src/pages/import_export/import/logic.rs
+++ b/web/src/pages/import_export/import/logic.rs
@@ -49,7 +49,14 @@ pub(crate) async fn import_csv(
Err(error) => return load_error(&headers, error),
};
if cross_site(&headers) {
- return (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response();
+ return (
+ StatusCode::FORBIDDEN,
+ tr!(
+ Locale::from_headers(&headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response();
}
let (profile_name, table_names) = match form.targets(Locale::from_headers(&headers)) {
Ok(targets) => targets,
diff --git a/web/src/pages/permissions/grants/loader.rs b/web/src/pages/permissions/grants/loader.rs
index 7a06e4cd..58922446 100644
--- a/web/src/pages/permissions/grants/loader.rs
+++ b/web/src/pages/permissions/grants/loader.rs
@@ -235,7 +235,7 @@ mod tests {
let direct = [permission("data:acme/*", "read")];
let effective = [permission("data:acme/*", "read")];
- let groups = group_objects(&objects, &direct, &effective);
+ let groups = group_objects(crate::i18n::Locale::default(), &objects, &direct, &effective);
assert_eq!(groups.len(), 2);
assert!(groups[0].global);
assert_eq!(groups[1].title, "acme");
diff --git a/web/src/schema/mod.rs b/web/src/schema/mod.rs
index f0491904..ba940624 100644
--- a/web/src/schema/mod.rs
+++ b/web/src/schema/mod.rs
@@ -1718,7 +1718,7 @@ pub(crate) mod tests {
draft.type_input = "temporal".to_string();
// Incomplete while no subtype is chosen.
- assert_eq!(draft.canonical_type_input().unwrap(), None);
+ assert_eq!(draft.canonical_type_input(crate::i18n::Locale::default()).unwrap(), None);
assert!(draft.show_temporal_type());
draft.temporal_type_input = "raw_datetime".to_string();
@@ -1735,7 +1735,7 @@ pub(crate) mod tests {
draft.name_input = "weight".to_string();
draft.type_input = "decimal".to_string();
- assert_eq!(draft.canonical_type_input().unwrap(), None);
+ assert_eq!(draft.canonical_type_input(crate::i18n::Locale::default()).unwrap(), None);
assert!(draft.show_decimal_arguments());
draft.decimal_precision_input = "12".to_string();
draft.decimal_scale_input = "3".to_string();
@@ -1750,7 +1750,7 @@ pub(crate) mod tests {
draft.type_input = "link".to_string();
assert!(draft.show_link_target());
- assert_eq!(draft.canonical_type_input().unwrap(), None);
+ assert_eq!(draft.canonical_type_input(crate::i18n::Locale::default()).unwrap(), None);
draft.link_table_input = "customer".to_string();
draft.add_from_inputs(crate::i18n::Locale::default()).unwrap();
@@ -1763,7 +1763,7 @@ pub(crate) mod tests {
#[test]
fn bare_link_type_is_rejected_without_a_target() {
assert_eq!(
- draft().catalog.validate_field_type("link"),
+ draft().catalog.validate_field_type(crate::i18n::Locale::default(), "link"),
Some("`link` needs a referenced table.".to_string())
);
}
@@ -1858,7 +1858,7 @@ pub(crate) mod tests {
assert!(draft.add_from_inputs(crate::i18n::Locale::default()).is_err());
assert!(
catalog()
- .validate_field_type("phone_calling_code")
+ .validate_field_type(crate::i18n::Locale::default(), "phone_calling_code")
.is_some()
);
}
@@ -2344,13 +2344,13 @@ pub(crate) mod tests {
assert_eq!(MAX_TABLE_NAME_LENGTH, 38);
let longest = "t".repeat(MAX_TABLE_NAME_LENGTH);
- assert_eq!(validate_table_name(&longest), None);
- assert_eq!(validate_identifier(&longest, "Column name", true), None);
+ assert_eq!(validate_table_name(crate::i18n::Locale::default(), &longest), None);
+ assert_eq!(validate_identifier(crate::i18n::Locale::default(), &longest, "Column name", true), None);
let too_long = "t".repeat(MAX_TABLE_NAME_LENGTH + 1);
- assert!(validate_table_name(&too_long).is_some());
+ assert!(validate_table_name(crate::i18n::Locale::default(), &too_long).is_some());
// Still a perfectly good column name, which is why the two differ.
- assert_eq!(validate_identifier(&too_long, "Column name", true), None);
+ assert_eq!(validate_identifier(crate::i18n::Locale::default(), &too_long, "Column name", true), None);
}
/// Every profile is given these tables when it is created, so a new table
@@ -2358,11 +2358,11 @@ pub(crate) mod tests {
#[test]
fn the_tables_every_profile_is_given_keep_their_names() {
for name in RESERVED_TABLE_NAMES {
- let error = validate_table_name(name)
+ let error = validate_table_name(crate::i18n::Locale::default(), name)
.unwrap_or_else(|| panic!("`{name}` should be reserved"));
assert!(error.contains(name), "{error}");
}
- assert_eq!(validate_table_name("invoice"), None);
+ assert_eq!(validate_table_name(crate::i18n::Locale::default(), "invoice"), None);
}
}
@@ -2374,7 +2374,7 @@ mod link_alias_tests {
/// derived from a table name any more.
#[test]
fn a_column_name_may_end_in_id() {
- assert_eq!(validate_identifier("external_id", "Column name", true), None);
+ assert_eq!(validate_identifier(crate::i18n::Locale::default(), "external_id", "Column name", true), None);
}
/// The system columns stay reserved, since they share one namespace with
@@ -2383,7 +2383,7 @@ mod link_alias_tests {
fn the_system_columns_stay_reserved() {
for name in ["id", "deleted", "created_at", "row_revision"] {
assert!(
- validate_identifier(name, "Column name", true).is_some(),
+ validate_identifier(crate::i18n::Locale::default(), name, "Column name", true).is_some(),
"`{name}` must stay reserved"
);
}
diff --git a/web/src/services/mod.rs b/web/src/services/mod.rs
index 98128514..ef7c7b5d 100644
--- a/web/src/services/mod.rs
+++ b/web/src/services/mod.rs
@@ -33,5 +33,14 @@ pub(crate) fn reject_cross_site(headers: &HeaderMap) -> Option {
headers
.get("sec-fetch-site")
.is_some_and(|value| value == "cross-site")
- .then(|| (StatusCode::FORBIDDEN, "Cross-site form submission rejected").into_response())
+ .then(|| {
+ (
+ StatusCode::FORBIDDEN,
+ crate::tr!(
+ crate::i18n::Locale::from_headers(headers),
+ "services-cross-site-rejected"
+ ),
+ )
+ .into_response()
+ })
}