diff --git a/Tools/langtool/.gitignore b/Tools/langtool/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/Tools/langtool/.gitignore @@ -0,0 +1 @@ +target diff --git a/Tools/langtool/Cargo.lock b/Tools/langtool/Cargo.lock new file mode 100644 index 0000000000..5c4ab6d00b --- /dev/null +++ b/Tools/langtool/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "langtool" +version = "0.1.0" diff --git a/Tools/langtool/Cargo.toml b/Tools/langtool/Cargo.toml new file mode 100644 index 0000000000..ad24cc8bb0 --- /dev/null +++ b/Tools/langtool/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "langtool" +version = "0.1.0" +authors = ["Henrik Rydgård "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/Tools/langtool/README.md b/Tools/langtool/README.md new file mode 100644 index 0000000000..667472552a --- /dev/null +++ b/Tools/langtool/README.md @@ -0,0 +1,17 @@ +# Language ini file processor tool + +Wrote it quickly in rust as a little exercise. + +To run, with rust installed: + +```rust +cargo run +``` + +To autoformat the code, use: + +```rust +cargo fmt +``` + +or set up your IDE to do that. diff --git a/Tools/langtool/src/main.rs b/Tools/langtool/src/main.rs new file mode 100644 index 0000000000..f17b2c227d --- /dev/null +++ b/Tools/langtool/src/main.rs @@ -0,0 +1,230 @@ +use std::fs::File; +use std::io::{self, BufRead, Write}; +use std::path::{Path, PathBuf}; + +// Super simplified ini file processor. +// Doesn't even bother with understanding comments. +// Just understands section headings and +// keys and values, split by ' = '. + +#[derive(Debug, Clone)] +struct Section { + name: String, + title_line: String, + lines: Vec, +} + +impl Section { + fn insert_line_if_missing(&mut self, line: &str) -> bool { + let prefix = if let Some(pos) = line.find(" =") { + &line[0..pos + 2] + } else { + return false; + }; + let prefix = prefix.to_owned(); + + for iter_line in &self.lines { + if iter_line.starts_with(&prefix) { + // Already have it + return false; + } + } + + // Now try to insert it at an alphabetic-ish location. + + // Then, find a suitable insertion spot + for (i, iter_line) in self.lines.iter().enumerate() { + if iter_line > &prefix { + println!("Inserting line {} into {}", line, self.name); + self.lines.insert(i, line.to_owned()); + return true; + } + } + println!("failed to insert {}", line); + return true; + } +} + +#[derive(Debug)] +struct IniFile { + filename: PathBuf, + preamble: Vec, + sections: Vec
, + has_bom: bool, +} + +impl IniFile { + fn parse(filename: &str) -> io::Result { + let lines = read_lines(filename)?; + + let mut sections = vec![]; + let mut preamble = vec![]; + let mut cur_section = None; + + let mut has_bom = false; + + for line in lines { + let line = line.unwrap(); + + let line = if line.starts_with("\u{feff}") { + has_bom = true; + &line[3..] + } else { + &line + }; + + if let Some('[') = line.chars().next() { + if let Some(right_bracket) = line.find(']') { + if let Some(section) = cur_section.take() { + sections.push(section); + } + + let name = &line[1..right_bracket]; + cur_section = Some(Section { + name: name.to_owned(), + title_line: line.to_owned(), // preserves comment and bom + lines: vec![], + }); + } else { + // Bad syntax + break; + } + } else { + if let Some(cur_section) = &mut cur_section { + cur_section.lines.push(line.to_owned()); + } else { + preamble.push(line.to_owned()); + } + } + } + + if let Some(section) = cur_section.take() { + sections.push(section); + } + + let ini = IniFile { + filename: PathBuf::from(filename), + preamble, + sections, + has_bom, + }; + Ok(ini) + } + + fn write(&self) -> io::Result<()> { + let file = std::fs::File::create(&self.filename)?; + let mut file = std::io::LineWriter::new(file); + + // Write BOM + if self.has_bom { + file.write_all("\u{feff}".as_bytes())?; + } + for line in &self.preamble { + file.write_all(line.as_bytes())?; + file.write(b"\n")?; + } + for section in &self.sections { + file.write_all(section.title_line.as_bytes())?; + file.write(b"\n")?; + for line in §ion.lines { + file.write_all(line.as_bytes())?; + file.write(b"\n")?; + } + } + + Ok(()) + } + + // Assumes alphabetical section order! + fn insert_section_if_missing(&mut self, section: &Section) -> bool { + // First, check if it's there. + + for iter_section in &self.sections { + if iter_section.name == section.name { + return false; + } + } + + // Then, find a suitable insertion spot + for (i, iter_section) in self.sections.iter_mut().enumerate() { + if iter_section.name > section.name { + println!("Inserting section {}", section.name); + self.sections.insert(i, section.clone()); + return true; + } + } + // Reached the end for some reason? Add it. + self.sections.push(section.clone()); + return true; + } + + fn get_section_mut(&mut self, section_name: &str) -> Option<&mut Section> { + for section in &mut self.sections { + if section.name == section_name { + return Some(section); + } + } + None + } +} + +// Grabbed from a sample, a fast line reader iterator. +fn read_lines

(filename: P) -> io::Result>> +where + P: AsRef, +{ + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} + +fn copy_missing_lines(reference_ini: &IniFile, target_ini: &mut IniFile) { + // Insert any missing full sections. + for section in &reference_ini.sections { + if !target_ini.insert_section_if_missing(section) { + if let Some(target_section) = target_ini.get_section_mut(§ion.name) { + for line in §ion.lines { + target_section.insert_line_if_missing(line); + } + } + } + } + + target_ini.write(); +} + +fn main() { + let args: Vec<_> = std::env::args().skip(1).collect(); + + let mut filenames = args; + + println!("{:?}", filenames); + + let root = "../../assets/lang"; + let reference_file = "en_US.ini"; + + let reference_ini = IniFile::parse(&format!("{}/{}", root, reference_file)).unwrap(); + + if filenames.is_empty() { + // Grab them all. + for path in std::fs::read_dir(root).unwrap() { + let path = path.unwrap(); + if path.file_name() == reference_file { + continue; + } + filenames.push(path.file_name().to_string_lossy().to_string()); + } + } + + for filename in filenames { + if filename == "langtool" || !filename.ends_with("ini") { + // Get this from cargo run for some reason. + continue; + } + let target_ini_filename = format!("{}/{}", root, filename); + println!("Langtool processing {}", target_ini_filename); + let mut target_ini = IniFile::parse(&target_ini_filename).unwrap(); + copy_missing_lines(&reference_ini, &mut target_ini); + } + + // println!("{:#?}", target_ini); +} diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index e249370b5c..d28cfcccaa 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -38,7 +38,6 @@ Combo Key Setting = コンボキーの設定 Combo Key Setup = コンボキーを設定する Confine Mouse = ウィンドウ/表示領域内でマウスをトラップする Control Mapping = キーを設定する -Customize Touch Controls = カスタムレイアウト... Customize tilt = 傾きをカスタマイズする Customize Touch Controls = 画面タッチ用コントローラーをカスタマイズする D-PAD = 方向キー