improve localization
This commit is contained in:
parent
1fd5ce890b
commit
95d38c5514
12 changed files with 108 additions and 52 deletions
|
@ -1,5 +0,0 @@
|
||||||
zone_records_title = Zone { $zone_name } records
|
|
||||||
|
|
||||||
record_type_address = Addresses
|
|
||||||
record_type_mailserver = E-mail servers
|
|
||||||
record_type_nameserver = Name servers
|
|
21
locales/en/main.ftl
Normal file
21
locales/en/main.ftl
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
zone-header = Zone { $zone_name }
|
||||||
|
|
||||||
|
zone-content-title = Zone content
|
||||||
|
zone-content-records-header = Records
|
||||||
|
zone-content-aliases-header = Aliases
|
||||||
|
|
||||||
|
zone-content-section-web-header = Web
|
||||||
|
zone-content-section-mail-header = E-mail
|
||||||
|
zone-content-section-general-header = General
|
||||||
|
|
||||||
|
zone-content-record-type-address =
|
||||||
|
.type-name = Addresses
|
||||||
|
|
||||||
|
zone-content-record-type-mailserver =
|
||||||
|
.type-name = E-mail servers
|
||||||
|
.data-preference = Preference: { $preference }
|
||||||
|
|
||||||
|
zone-content-record-type-nameserver =
|
||||||
|
.type-name = Name servers
|
||||||
|
|
||||||
|
zone-content-new-record-button = New record
|
|
@ -1,5 +0,0 @@
|
||||||
zone_records_title = Enregistrements de la zone { $zone_name }
|
|
||||||
|
|
||||||
record_type_address = Adresses
|
|
||||||
record_type_mailserver = Serveurs de courriel
|
|
||||||
record_type_nameserver = Serveurs de noms
|
|
22
locales/fr/main.ftl
Normal file
22
locales/fr/main.ftl
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
zone-header = Zone { $zone_name }
|
||||||
|
|
||||||
|
zone-content-title = Contenu de la zone
|
||||||
|
zone-content-records-header = Enregistrements
|
||||||
|
zone-content-aliases-header = Alias
|
||||||
|
|
||||||
|
zone-content-section-web-header = Web
|
||||||
|
zone-content-section-mail-header = Courriel
|
||||||
|
zone-content-section-general-header = Général
|
||||||
|
|
||||||
|
zone-content-record-type-address =
|
||||||
|
.type-name = Adresses
|
||||||
|
|
||||||
|
zone-content-record-type-mailserver =
|
||||||
|
.type-name = Serveurs de courriel
|
||||||
|
.data-preference = Préférence : { $preference }
|
||||||
|
|
||||||
|
zone-content-record-type-nameserver =
|
||||||
|
.type-name = Serveurs de noms
|
||||||
|
|
||||||
|
zone-content-new-record-button = Nouvel enregistrement
|
|
@ -1,34 +1,52 @@
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, fs, path::Path, str::FromStr};
|
||||||
|
|
||||||
use fluent_bundle::{concurrent::FluentBundle, FluentArgs, FluentResource, FluentValue};
|
use fluent_bundle::{concurrent::FluentBundle, FluentArgs, FluentResource, FluentValue};
|
||||||
use unic_langid::LanguageIdentifier;
|
use unic_langid::LanguageIdentifier;
|
||||||
|
|
||||||
const SOURCES: &[(&str, &str)] = &[
|
|
||||||
("en", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/en.ftl"))),
|
|
||||||
("fr", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/fr.ftl"))),
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Localization {
|
pub struct Localization {
|
||||||
bundles: std::sync::Arc<HashMap<LanguageIdentifier, FluentBundle<FluentResource>>>,
|
bundles: std::sync::Arc<HashMap<LanguageIdentifier, FluentBundle<FluentResource>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Localization {
|
impl Localization {
|
||||||
pub fn init() -> Self {
|
pub fn init(locale_directory: &Path) -> Self {
|
||||||
let mut bundles = HashMap::new();
|
let mut bundles = HashMap::new();
|
||||||
for (locale, translations) in SOURCES {
|
let directory_content =fs::read_dir(locale_directory)
|
||||||
let res = FluentResource::try_new(translations.to_string()).expect("Failed to parse an FTL string.");
|
.expect("Unable to read locales directory");
|
||||||
let langid: LanguageIdentifier = locale.parse().expect("Parsing failed");
|
|
||||||
|
for lang_dir in directory_content {
|
||||||
|
let lang_dir = lang_dir.expect("I/O error");
|
||||||
|
if lang_dir.path().is_dir() {
|
||||||
|
let langid: LanguageIdentifier = lang_dir.file_name()
|
||||||
|
.into_string()
|
||||||
|
.expect("String convertion failed")
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to parse language tag");
|
||||||
|
|
||||||
let mut bundle = FluentBundle::new_concurrent(vec![langid.clone()]);
|
let mut bundle = FluentBundle::new_concurrent(vec![langid.clone()]);
|
||||||
|
|
||||||
|
let directory_content = fs::read_dir(lang_dir.path())
|
||||||
|
.expect("Unable to read locales directory");
|
||||||
|
for resource in directory_content {
|
||||||
|
let resource = resource.expect("I/O error");
|
||||||
|
|
||||||
|
if resource.path().is_file() {
|
||||||
|
let resource = fs::read_to_string(resource.path())
|
||||||
|
.expect("Failed to open file");
|
||||||
|
|
||||||
|
let resource = FluentResource::try_new(resource)
|
||||||
|
.expect("Failed to parse an FTL string.");
|
||||||
|
|
||||||
bundle
|
bundle
|
||||||
.add_resource(res)
|
.add_resource(resource)
|
||||||
.expect("Failed to add FTL resources to the bundle.");
|
.expect("Failed to add FTL resources to the bundle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bundles.insert(langid, bundle);
|
bundles.insert(langid, bundle);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Localization {
|
Localization {
|
||||||
bundles: std::sync::Arc::new(bundles),
|
bundles: std::sync::Arc::new(bundles),
|
||||||
|
@ -54,20 +72,29 @@ impl tera::Function for Localization {
|
||||||
.and_then(|lang| lang.as_str())
|
.and_then(|lang| lang.as_str())
|
||||||
.ok_or(tera::Error::msg("localize: Missing msg parameter"))?;
|
.ok_or(tera::Error::msg("localize: Missing msg parameter"))?;
|
||||||
|
|
||||||
|
let attribute = args.get("attr")
|
||||||
|
.and_then(|lang| lang.as_str());
|
||||||
|
|
||||||
let bundle = self.bundles.get(&locale)
|
let bundle = self.bundles.get(&locale)
|
||||||
.ok_or_else(|| tera::Error::msg("localize: Could not find locale {locale}"))?;
|
.ok_or_else(|| tera::Error::msg(format!("localize: Could not find locale {locale}")))?;
|
||||||
|
|
||||||
let message = bundle.get_message(msg)
|
let message = bundle.get_message(msg)
|
||||||
.ok_or_else(|| tera::Error::msg("localize: Could not find message {msg}"))?;
|
.ok_or_else(|| tera::Error::msg(format!("localize: Could not find message {msg}")))?;
|
||||||
|
|
||||||
|
|
||||||
let pattern = message.value()
|
let pattern = if let Some(attribute) = attribute {
|
||||||
.ok_or_else(|| tera::Error::msg("localize: Message {msg} has no value"))?;
|
message.get_attribute(attribute)
|
||||||
|
.map(|attribute| attribute.value())
|
||||||
|
.ok_or_else(|| tera::Error::msg(format!("localize: Attribute {msg}.{attribute} has no value")))?
|
||||||
|
} else {
|
||||||
|
message.value()
|
||||||
|
.ok_or_else(|| tera::Error::msg(format!("localize: Message {msg} has no value")))?
|
||||||
|
};
|
||||||
|
|
||||||
let mut msg_args = FluentArgs::new();
|
let mut msg_args = FluentArgs::new();
|
||||||
|
|
||||||
for (key, value) in args {
|
for (key, value) in args {
|
||||||
if key != "msg" && key != "lang" {
|
if key != "msg" && key != "lang" && key != "attr" {
|
||||||
msg_args.set(key, fluent_value_from_tera(value, key)?);
|
msg_args.set(key, fluent_value_from_tera(value, key)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ mod validation;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod template;
|
mod template;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod locale;
|
mod localization;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ pub struct AppState {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let localization = locale::Localization::init();
|
let localization = localization::Localization::init(std::path::Path::new("./locales"));
|
||||||
|
|
||||||
let template_engine = TemplateEngine::new(
|
let template_engine = TemplateEngine::new(
|
||||||
std::path::Path::new("./templates"),
|
std::path::Path::new("./templates"),
|
||||||
|
|
1
src/resources/dns/external/mod.rs
vendored
1
src/resources/dns/external/mod.rs
vendored
|
@ -1,5 +1,4 @@
|
||||||
pub mod rdata;
|
pub mod rdata;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
|
|
||||||
pub use rdata::*;
|
|
||||||
pub use record::*;
|
pub use record::*;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use axum::extract::Request;
|
|
||||||
use axum::extract::{Query, Path, State, OriginalUri};
|
use axum::extract::{Query, Path, State, OriginalUri};
|
||||||
use axum::Extension;
|
use axum::Extension;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -21,8 +20,6 @@ pub async fn get_records_page(
|
||||||
let records = zone.get_records(app.records).await?;
|
let records = zone.get_records(app.records).await?;
|
||||||
let records = friendly::FriendlyRecords::from(records.clone());
|
let records = friendly::FriendlyRecords::from(records.clone());
|
||||||
|
|
||||||
println!("{}", lang);
|
|
||||||
|
|
||||||
Ok(Template::new(
|
Ok(Template::new(
|
||||||
"pages/records.html",
|
"pages/records.html",
|
||||||
app.template_engine,
|
app.template_engine,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use axum::response::{Html, IntoResponse};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tera::{Tera, Context};
|
use tera::{Tera, Context};
|
||||||
|
|
||||||
use crate::{errors::Error, locale::Localization};
|
use crate::{errors::Error, localization::Localization};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -19,8 +19,8 @@ pub fn normalize_domain(domain_name: &str) -> Result<String, Error> {
|
||||||
|
|
||||||
let domain = domain_name.strip_suffix('.').unwrap_or(domain_name).to_lowercase();
|
let domain = domain_name.strip_suffix('.').unwrap_or(domain_name).to_lowercase();
|
||||||
|
|
||||||
if domain.as_bytes().len() > 255 {
|
if domain.len() > 255 {
|
||||||
Err(Error::from(DomainValidationError::DomainTooLong { length: domain.as_bytes().len() }))
|
Err(Error::from(DomainValidationError::DomainTooLong { length: domain.len() }))
|
||||||
} else {
|
} else {
|
||||||
let labels = domain.split('.').collect::<Vec<_>>();
|
let labels = domain.split('.').collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -40,10 +40,10 @@ pub fn normalize_domain(domain_name: &str) -> Result<String, Error> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if label.as_bytes().len() > 63 {
|
if label.len() > 63 {
|
||||||
return Err(Error::from(DomainValidationError::LabelToolLong {
|
return Err(Error::from(DomainValidationError::LabelToolLong {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
length: label.as_bytes().len()
|
length: label.len()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% macro rrset(rtype, ttl, data, zone, lang) %}
|
{% macro rrset(rtype, ttl, data, zone, lang) %}
|
||||||
<li class="rrset">
|
<li class="rrset">
|
||||||
<div class="rtype">
|
<div class="rtype">
|
||||||
{{ tr(msg="record_type_" ~ rtype, lang=lang) }}
|
{{ tr(msg="zone-content-record-type-" ~ rtype, attr="type-name", lang=lang) }}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button class="icon">
|
<button class="icon">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="rdata-complementary">
|
<div class="rdata-complementary">
|
||||||
<span class="pill">
|
<span class="pill">
|
||||||
Preference: {{ data.preference }}
|
{{ tr(msg="zone-content-record-type-mailserver", attr="data-preference", preference=data.preference, lang=lang) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% elif rtype == "nameserver" %}
|
{% elif rtype == "nameserver" %}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{% import "macros.html" as macros %}
|
{% import "macros.html" as macros %}
|
||||||
{% extends "bases/app.html" %}
|
{% extends "bases/app.html" %}
|
||||||
|
|
||||||
{% block title %}Records - {{ current_zone }} - {% endblock title %}
|
{% block title %}{{ tr(msg="zone-content-title", lang=lang) }} - {{ current_zone }} - {% endblock title %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>{{ tr(msg="zone_records_title", lang=lang, zone_name="<strong>" ~ current_zoned ~ "</strong>") | safe }}</h1>
|
<h1>{{ tr(msg="zone-header", lang=lang, zone_name="<strong>" ~ current_zone ~ "</strong>") | safe }}</h1>
|
||||||
<svg width="0" height="0" aria-hidden="true" style="position: absolute;">
|
<svg width="0" height="0" aria-hidden="true" style="position: absolute;">
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="corner-folder-tab-right" clipPathUnits="objectBoundingBox">
|
<clipPath id="corner-folder-tab-right" clipPathUnits="objectBoundingBox">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<section>
|
<section>
|
||||||
<h2>Records</h2>
|
<h2>{{ tr(msg="zone-content-records-header", lang=lang) }}</h2>
|
||||||
{% for group in records.records %}
|
{% for group in records.records %}
|
||||||
<article class="domain">
|
<article class="domain">
|
||||||
<header>
|
<header>
|
||||||
|
@ -24,12 +24,12 @@
|
||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
|
||||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>
|
||||||
</svg>
|
</svg>
|
||||||
Add record
|
{{ tr(msg="zone-content-new-record-button", lang=lang) }}
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<div class="records">
|
<div class="records">
|
||||||
{% if group.web %}
|
{% if group.web %}
|
||||||
<h4>Web</h4>
|
<h4>{{ tr(msg="zone-content-section-web-header", lang=lang) }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% if group.web.addresses %}
|
{% if group.web.addresses %}
|
||||||
{{ macros::rrset(
|
{{ macros::rrset(
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if group.mail %}
|
{% if group.mail %}
|
||||||
<h4>E-mails</h4>
|
<h4>{{ tr(msg="zone-content-section-mail-header", lang=lang) }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% if group.mail.servers %}
|
{% if group.mail.servers %}
|
||||||
{{ macros::rrset(
|
{{ macros::rrset(
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if group.general_records %}
|
{% if group.general_records %}
|
||||||
<h4>General</h4>
|
<h4>{{ tr(msg="zone-content-section-general-header", lang=lang) }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% for rrset in group.general_records %}
|
{% for rrset in group.general_records %}
|
||||||
{{ macros::rrset(
|
{{ macros::rrset(
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Aliases</h2>
|
<h2>{{ tr(msg="zone-content-aliases-header", lang=lang) }}</h2>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for alias in records.aliases %}
|
{% for alias in records.aliases %}
|
||||||
|
|
Loading…
Reference in a new issue