fcb3283a9d
This avoids the need to modify the external repo before new strings can be used in aqt.
271 lines
7.7 KiB
Rust
271 lines
7.7 KiB
Rust
use fluent_syntax::ast::Entry;
|
|
use fluent_syntax::parser::Parser;
|
|
use std::path::Path;
|
|
use std::{collections::HashMap, env};
|
|
use std::{fs, path::PathBuf};
|
|
|
|
fn get_identifiers(ftl_text: &str) -> Vec<String> {
|
|
let res = Parser::new(ftl_text).parse().unwrap();
|
|
let mut idents = vec![];
|
|
|
|
for entry in res.body {
|
|
if let Entry::Message(m) = entry {
|
|
idents.push(m.id.name.to_string());
|
|
}
|
|
}
|
|
|
|
idents.sort_unstable();
|
|
|
|
idents
|
|
}
|
|
|
|
fn proto_enum(idents: &[String]) -> String {
|
|
let mut buf = String::from(
|
|
r#"// This file is automatically generated as part of the build process.
|
|
|
|
syntax = "proto3";
|
|
package FluentProto;
|
|
enum FluentString {
|
|
"#,
|
|
);
|
|
for (idx, s) in idents.iter().enumerate() {
|
|
let name = s.replace("-", "_").to_uppercase();
|
|
buf += &format!(" {} = {};\n", name, idx);
|
|
}
|
|
|
|
buf += "}\n";
|
|
|
|
buf
|
|
}
|
|
|
|
fn rust_string_vec(idents: &[String]) -> String {
|
|
let mut buf = String::from(
|
|
r#"// This file is automatically generated as part of the build process.
|
|
|
|
pub(super) const FLUENT_KEYS: &[&str] = &[
|
|
"#,
|
|
);
|
|
|
|
for s in idents {
|
|
buf += &format!(" \"{}\",\n", s);
|
|
}
|
|
|
|
buf += "];\n";
|
|
|
|
buf
|
|
}
|
|
|
|
struct FTLData {
|
|
templates: Vec<String>,
|
|
/// lang -> [FileContent]
|
|
translations: HashMap<String, Vec<String>>,
|
|
}
|
|
|
|
impl FTLData {
|
|
fn add_language_folder(&mut self, folder: &Path) {
|
|
let lang = folder.file_name().unwrap().to_str().unwrap();
|
|
let list = self.translations.entry(lang.to_string()).or_default();
|
|
for entry in fs::read_dir(&folder).unwrap() {
|
|
let entry = entry.unwrap();
|
|
let text = fs::read_to_string(&entry.path()).unwrap();
|
|
assert!(
|
|
text.ends_with('\n'),
|
|
"file was missing final newline: {:?}",
|
|
entry
|
|
);
|
|
list.push(text);
|
|
}
|
|
}
|
|
|
|
fn add_template_folder(&mut self, folder: &Path) {
|
|
for entry in fs::read_dir(&folder).unwrap() {
|
|
let entry = entry.unwrap();
|
|
let text = fs::read_to_string(&entry.path()).unwrap();
|
|
assert!(
|
|
text.ends_with('\n'),
|
|
"file was missing final newline: {:?}",
|
|
entry
|
|
);
|
|
self.templates.push(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_ftl_data() -> FTLData {
|
|
let mut data = get_ftl_data_from_source_tree();
|
|
|
|
let rslib_l10n = std::env::var("RSLIB_FTL_ROOT").ok();
|
|
let extra_l10n = std::env::var("EXTRA_FTL_ROOT").ok();
|
|
|
|
// core translations provided?
|
|
if let Some(path) = rslib_l10n {
|
|
let path = Path::new(&path);
|
|
let core_folder = path.with_file_name("core");
|
|
for entry in fs::read_dir(&core_folder).unwrap() {
|
|
let entry = entry.unwrap();
|
|
if entry.file_name().to_str().unwrap() == "templates" {
|
|
// ignore source ftl files, as we've already extracted them from the source tree
|
|
continue;
|
|
}
|
|
data.add_language_folder(&entry.path());
|
|
}
|
|
}
|
|
|
|
// extra templates/translations provided?
|
|
if let Some(path) = extra_l10n {
|
|
let mut path = PathBuf::from(path);
|
|
// drop l10n.toml filename to get folder
|
|
path.pop();
|
|
// look for subfolders
|
|
for outer_entry in fs::read_dir(&path).unwrap() {
|
|
let outer_entry = outer_entry.unwrap();
|
|
if outer_entry.file_type().unwrap().is_dir() {
|
|
// process folder
|
|
for entry in fs::read_dir(&outer_entry.path()).unwrap() {
|
|
let entry = entry.unwrap();
|
|
if entry.file_name().to_str().unwrap() == "templates" {
|
|
if include_local_qt_templates() {
|
|
// ignore source ftl files, as we've already extracted them from the source tree
|
|
continue;
|
|
}
|
|
data.add_template_folder(&entry.path());
|
|
} else {
|
|
data.add_language_folder(&entry.path());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
data
|
|
}
|
|
|
|
/// In a standard build, the ftl/qt folder is used as the source
|
|
/// of truth for @extra_ftl, making it easier to add new strings.
|
|
/// If the Qt templates are not desired, the NO_QT_TEMPLATES env
|
|
/// var can be set to skip them.
|
|
fn include_local_qt_templates() -> bool {
|
|
env::var("NO_QT_TEMPLATES").is_err()
|
|
}
|
|
|
|
/// Extracts English text from ftl folder in source tree.
|
|
fn get_ftl_data_from_source_tree() -> FTLData {
|
|
let mut templates: Vec<String> = vec![];
|
|
|
|
let ftl_base = if let Ok(srcfile) = env::var("FTL_SRC") {
|
|
let mut path = PathBuf::from(srcfile);
|
|
path.pop();
|
|
path
|
|
} else {
|
|
PathBuf::from("../ftl")
|
|
};
|
|
|
|
let dir = ftl_base.join("core");
|
|
for entry in fs::read_dir(dir).unwrap() {
|
|
let entry = entry.unwrap();
|
|
let fname = entry.file_name().into_string().unwrap();
|
|
if fname.ends_with(".ftl") {
|
|
templates.push(fs::read_to_string(entry.path()).unwrap());
|
|
}
|
|
}
|
|
|
|
if include_local_qt_templates() {
|
|
let dir = ftl_base.join("qt");
|
|
for entry in fs::read_dir(dir).unwrap() {
|
|
let entry = entry.unwrap();
|
|
let fname = entry.file_name().into_string().unwrap();
|
|
if fname.ends_with(".ftl") {
|
|
templates.push(fs::read_to_string(entry.path()).unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
FTLData {
|
|
templates,
|
|
translations: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Map of lang->content; Template lang is "template".
|
|
fn merge_ftl_data(data: FTLData) -> HashMap<String, String> {
|
|
data.translations
|
|
.into_iter()
|
|
.map(|(lang, content)| (lang, content.join("\n")))
|
|
.chain(std::iter::once((
|
|
"template".to_string(),
|
|
data.templates.join("\n"),
|
|
)))
|
|
.collect()
|
|
}
|
|
|
|
fn write_merged_ftl_files(dir: &Path, data: &HashMap<String, String>) {
|
|
for (lang, content) in data {
|
|
let path = dir.join(format!("{}.ftl", lang));
|
|
fs::write(&path, content).unwrap();
|
|
}
|
|
}
|
|
|
|
fn write_fluent_keys_rs(dir: &Path, idents: &[String]) {
|
|
let path = dir.join("fluent_keys.rs");
|
|
fs::write(&path, rust_string_vec(idents)).unwrap();
|
|
}
|
|
|
|
fn write_fluent_proto_inner(path: &Path, idents: &[String]) {
|
|
fs::write(&path, proto_enum(idents)).unwrap();
|
|
}
|
|
|
|
/// Write fluent.proto into the provided dir.
|
|
/// Can be called separately to provide a proto
|
|
/// to downstream code.
|
|
pub fn write_fluent_proto(out_path: &str) {
|
|
let merged_ftl = merge_ftl_data(get_ftl_data());
|
|
let idents = get_identifiers(merged_ftl.get("template").unwrap());
|
|
write_fluent_proto_inner(Path::new(out_path), &idents);
|
|
}
|
|
|
|
/// Write all ftl-related files into OUT_DIR.
|
|
pub fn write_ftl_files_and_fluent_rs() {
|
|
let dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
let merged_ftl = merge_ftl_data(get_ftl_data());
|
|
write_merged_ftl_files(&dir, &merged_ftl);
|
|
|
|
let idents = get_identifiers(merged_ftl.get("template").unwrap());
|
|
write_fluent_keys_rs(&dir, &idents);
|
|
write_fluent_proto_inner(&dir.join("fluent.proto"), &idents);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn all() {
|
|
let idents = get_identifiers("key-one = foo\nkey-two = bar");
|
|
assert_eq!(idents, vec!["key-one", "key-two"]);
|
|
|
|
assert_eq!(
|
|
proto_enum(&idents),
|
|
r#"// This file is automatically generated as part of the build process.
|
|
|
|
syntax = "proto3";
|
|
package backend_strings;
|
|
enum FluentString {
|
|
KEY_ONE = 0;
|
|
KEY_TWO = 1;
|
|
}
|
|
"#
|
|
);
|
|
|
|
assert_eq!(
|
|
rust_string_vec(&idents),
|
|
r#"// This file is automatically generated as part of the build process.
|
|
|
|
const FLUENT_KEYS: &[&str] = &[
|
|
"key-one",
|
|
"key-two",
|
|
];
|
|
"#
|
|
);
|
|
}
|
|
}
|