i18n on the web - deepseek translations2
This commit is contained in:
@@ -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 <a href="/login">přihlaste</a>.
|
||||
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
|
||||
|
||||
@@ -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 <a href="/login">log in</a> 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
|
||||
|
||||
@@ -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 <a href="/login">prihláste</a>.
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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""#));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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""#));
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,5 +33,14 @@ pub(crate) fn reject_cross_site(headers: &HeaderMap) -> Option<Response> {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user