From 9942ca6f7ad6126ab7faa5a65d09c853c6383469 Mon Sep 17 00:00:00 2001 From: Liam Bigelow <40188355+bglw@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:29:41 +1200 Subject: [PATCH 1/7] Implement the URL rewriting feature --- rosey/Cargo.lock | 1 + rosey/Cargo.toml | 1 + .../features/build/rosey-build-links.feature | 8 + .../rosey-build-translated-links.feature | 119 +++++++++++++++ .../generate/rosey-generate-urls.feature | 31 ++++ rosey/src/lib.rs | 18 +++ rosey/src/main.rs | 9 ++ rosey/src/options.rs | 3 + rosey/src/runners/builder.rs | 9 +- rosey/src/runners/builder/html.rs | 140 +++++++++++++++--- rosey/src/runners/builder/html/utils.rs | 4 + rosey/src/runners/generator.rs | 20 +++ rosey/src/runners/generator/html.rs | 2 + rosey/test.sh | 4 +- 14 files changed, 344 insertions(+), 25 deletions(-) create mode 100644 rosey/features/build/rosey-build-translated-links.feature create mode 100644 rosey/features/generate/rosey-generate-urls.feature diff --git a/rosey/Cargo.lock b/rosey/Cargo.lock index ece80d1..e1e9094 100644 --- a/rosey/Cargo.lock +++ b/rosey/Cargo.lock @@ -2074,6 +2074,7 @@ dependencies = [ "serde_json", "sha2", "tokio", + "url", ] [[package]] diff --git a/rosey/Cargo.toml b/rosey/Cargo.toml index dbc8d8a..79233f4 100644 --- a/rosey/Cargo.toml +++ b/rosey/Cargo.toml @@ -24,3 +24,4 @@ actix-files = "0.6" tokio = { version = "1.36", features = ["macros", "rt-multi-thread", "time"] } notify = "5.0.0" charabia = "0.6.0" +url = "2" diff --git a/rosey/features/build/rosey-build-links.feature b/rosey/features/build/rosey-build-links.feature index 8f45844..a08e6b9 100644 --- a/rosey/features/build/rosey-build-links.feature +++ b/rosey/features/build/rosey-build-links.feature @@ -15,6 +15,8 @@ Feature: Rosey Links

Hello World Asset

Hello World Relative
Hello World Extension
+ Hello World Anchor + Hello World Query """ @@ -42,6 +44,12 @@ Feature: Rosey Links Then I should see a selector 'h6>a' in "dist/translated_site/blank/index.html" with the attributes: | href | /blank/posts/hello-world.html | | innerText | Hello World Extension | + Then I should see a selector 'h7>a' in "dist/translated_site/blank/index.html" with the attributes: + | href | /blank/posts/hello-world/#title | + | innerText | Hello World Anchor | + Then I should see a selector 'h8>a' in "dist/translated_site/blank/index.html" with the attributes: + | href | /blank/posts/hello-world.html?q=a | + | innerText | Hello World Query | Scenario: Rosey doesn't update links already pointing to a locale diff --git a/rosey/features/build/rosey-build-translated-links.feature b/rosey/features/build/rosey-build-translated-links.feature new file mode 100644 index 0000000..cc06272 --- /dev/null +++ b/rosey/features/build/rosey-build-translated-links.feature @@ -0,0 +1,119 @@ +Feature: Rosey Translated Links + Background: + Given I have the environment variables: + | ROSEY_SOURCE | dist/site | + | ROSEY_DEST | dist/translated_site | + Given I have a "dist/site/index.html" file with the content: + """ + + +

Hello World

+

Hello World Trailing Slash

+

Hello World Direct Index Link

+

Hello World Relative

+
Foobar
+
Foobar Trailing Slash
+ Foobar Direct Index Link + + + """ + And I have a "dist/site/posts/hello-world/index.html" file with the content: + """ + + +

Hello

+

Foobar Trailing Slash

+ + + """ + And I have a "dist/site/posts/foobar/index.html" file with the content: + """ + + +

Hello

+ + + """ + And I have a "rosey/locales/cool.json" file with the content: + """ + {} + """ + And I have a "rosey/locales/cool.urls.json" file with the content: + """ + { + "index.html": { + "original": "index.html", + "value": "index.html" + }, + "posts/hello-world/index.html": { + "original": "posts/hello-world/index.html", + "value": "radical-articles/sup-world/index.html" + }, + "posts/foobar/index.html": { + "original": "posts/foobar/index.html", + "value": "radical-articles/foobar/foobar.html" + } + } + """ + + Scenario: Rosey updates internal links using translation keys + When I run my program with the flags: + | build | + Then I should see a selector 'h1>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/sup-world/ | + | innerText | Hello World | + Then I should see a selector 'h2>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/sup-world/ | + | innerText | Hello World Trailing Slash | + Then I should see a selector 'h3>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/sup-world/ | + | innerText | Hello World Direct Index Link | + Then I should see a selector 'h4>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | posts/hello-world/ | + | innerText | Hello World Relative | + Then I should see a selector 'h5>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/foobar/foobar.html | + | innerText | Foobar | + Then I should see a selector 'h6>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/foobar/foobar.html | + | innerText | Foobar Trailing Slash | + Then I should see a selector 'h7>a' in "dist/translated_site/cool/index.html" with the attributes: + | href | /cool/radical-articles/foobar/foobar.html | + | innerText | Foobar Direct Index Link | + Then I should see a selector 'h2>a' in "dist/translated_site/cool/radical-articles/sup-world/index.html" with the attributes: + | href | /cool/radical-articles/foobar/foobar.html | + | innerText | Foobar Trailing Slash | + + Scenario: Rosey updates meta urls using translation keys + When I run my program with the flags: + | build | + Then I should see a selector 'link' in "dist/translated_site/posts/hello-world/index.html" with the attributes: + | rel | alternate | + | href | /cool/radical-articles/sup-world/ | + | hreflang | cool | + + Then I should see a selector 'link' in "dist/translated_site/en/posts/hello-world/index.html" with the attributes: + | rel | alternate | + | href | /cool/radical-articles/sup-world/ | + | hreflang | cool | + Then I should see a selector 'link' in "dist/translated_site/cool/radical-articles/sup-world/index.html" with the attributes: + | rel | alternate | + | href | /en/posts/hello-world/ | + | hreflang | en | + + Scenario: Rosey generates redirects with translation keys + When I run my program with the flags: + | build | + | --default-language "cool" | + Then I should see a selector 'a' in "dist/translated_site/posts/hello-world/index.html" with the attributes: + | href | /cool/radical-articles/sup-world/ | + | innerText | Click here if you are not redirected. | + + Scenario: Rosey generates canonicals with translation keys + When I run my program with the flags: + | build | + | --default-language "cool" | + Then I should see a selector 'link' in "dist/translated_site/posts/hello-world/index.html" with the attributes: + | rel | canonical | + | href | /cool/radical-articles/sup-world/ | + | hreflang | cool | diff --git a/rosey/features/generate/rosey-generate-urls.feature b/rosey/features/generate/rosey-generate-urls.feature new file mode 100644 index 0000000..0aeec5c --- /dev/null +++ b/rosey/features/generate/rosey-generate-urls.feature @@ -0,0 +1,31 @@ +Feature: Rosey Generate URLs + Background: + Given I have the environment variables: + | ROSEY_SOURCE | dist/site | + | ROSEY_DEST | dist/translated_site | + + Scenario: Rosey generates a URLs file + Given I have a "dist/site/index.html" file with the content: + """ + + +

Kiss From A Rose

+

Desert Rose

+ + + """ + And I have a "dist/site/about/index.html" file with the content: + """ + + +

Kiss From A Rose

+

Kiss From A Rose

+ + + """ + When I run my program with the flags: + | generate | + Then I should see "rosey/base.urls.json" containing the values: + | version | int:2 | + | keys.index\.html.original | index.html | + | keys.about/index\.html.original | about/index.html | diff --git a/rosey/src/lib.rs b/rosey/src/lib.rs index c9c41f3..b4a345a 100644 --- a/rosey/src/lib.rs +++ b/rosey/src/lib.rs @@ -107,6 +107,7 @@ impl RoseyOptions { separator: matches.get("separator", base.separator), locales: working_dir.join(matches.get("locales", base.locales)), base: working_dir.join(matches.get("base", base.base)), + base_urls: working_dir.join(matches.get("base-urls", base.base_urls)), default_language: matches.get("default-language", base.default_language), redirect_page: matches .get_opt("redirect-page", base.redirect_page) @@ -200,6 +201,19 @@ impl RoseyTranslation { } } + pub fn insert_uncounted(&mut self, key: String, value: String) { + match self { + RoseyTranslation::V1(keys) => { + keys.insert(key, value); + } + RoseyTranslation::V2(keys) => { + let translation = keys + .entry(key) + .or_insert_with(|| RoseyTranslationEntry::new(value)); + } + } + } + pub fn get(&self, key: &str) -> Option<&String> { match self { RoseyTranslation::V1(keys) => keys.get(key), @@ -302,6 +316,10 @@ impl RoseyLocale { self.keys.insert(key, value, page); } + pub fn insert_uncounted(&mut self, key: String, value: String) { + self.keys.insert_uncounted(key, value); + } + pub fn output(&mut self, version: u8) -> String { match version { 2 => serde_json::to_string_pretty(self).unwrap(), diff --git a/rosey/src/main.rs b/rosey/src/main.rs index 1e991f2..d546ce3 100644 --- a/rosey/src/main.rs +++ b/rosey/src/main.rs @@ -58,6 +58,15 @@ async fn main() { example_defaults.base.display() )), ) + .arg( + Arg::with_name("base-urls") + .long("base-urls") + .value_name("PATH") + .help(&format!( + "The file to generate the Rosey base URLs file to. \n ─ Defaults to '{}'", + example_defaults.base_urls.display() + )), + ) .arg( Arg::with_name("separator") .long("separator") diff --git a/rosey/src/options.rs b/rosey/src/options.rs index b0772f2..c3cdcee 100644 --- a/rosey/src/options.rs +++ b/rosey/src/options.rs @@ -15,6 +15,7 @@ pub struct RoseyPublicConfig { pub tag: String, pub separator: String, pub base: PathBuf, + pub base_urls: PathBuf, pub locales: PathBuf, pub languages: Option>, pub exclusions: String, @@ -35,6 +36,7 @@ impl Default for RoseyPublicConfig { tag: "data-rosey".into(), separator: ":".into(), base: "rosey/base.json".into(), + base_urls: "rosey/base.urls.json".into(), locales: "rosey/locales".into(), languages: None, exclusions: r#"\.(html?|json)$"#.into(), @@ -69,6 +71,7 @@ impl Display for RoseyPublicConfig { writeln!(f, " - Source: {}", self.source.display())?; writeln!(f, " - Destination: {}", self.dest.display())?; writeln!(f, " - Base locale file: {}", self.base.display())?; + writeln!(f, " - Base urls file: {}", self.base_urls.display())?; writeln!(f, " - Locales directory: {}", self.locales.display())?; match &self.images_source { Some(s) => writeln!(f, " - Images source: {}", s.display())?, diff --git a/rosey/src/runners/builder.rs b/rosey/src/runners/builder.rs index 09245a7..5fef53a 100644 --- a/rosey/src/runners/builder.rs +++ b/rosey/src/runners/builder.rs @@ -22,6 +22,7 @@ use crate::{RoseyOptions, RoseyTranslation}; pub struct RoseyBuilder { options: RoseyOptions, pub translations: BTreeMap, + pub url_translations: BTreeMap, } impl From for RoseyBuilder { @@ -29,6 +30,7 @@ impl From for RoseyBuilder { RoseyBuilder { options, translations: BTreeMap::default(), + url_translations: BTreeMap::default(), } } } @@ -176,7 +178,12 @@ impl RoseyBuilder { let value = read_to_string(file.path()).expect("Failed to read locale file"); let value = serde_json::from_str(&value); if let Ok(value) = value { - self.translations.insert(locale, value); + if locale.ends_with(".urls") { + self.url_translations + .insert(locale.trim_end_matches(".urls").to_string(), value); + } else { + self.translations.insert(locale, value); + } } }); } diff --git a/rosey/src/runners/builder/html.rs b/rosey/src/runners/builder/html.rs index 05d8d43..33407e3 100644 --- a/rosey/src/runners/builder/html.rs +++ b/rosey/src/runners/builder/html.rs @@ -26,6 +26,8 @@ use html5ever::{ }; use kuchiki::{traits::TendrilSink, Attribute, ExpandedName, NodeRef}; use sha2::{Digest, Sha256}; +use url::Url; +use utils::filepath_to_output_url; use crate::RoseyTranslation; @@ -55,46 +57,73 @@ impl RoseyBuilder { //If the file is already in a locale folder, then output it only for that locale if let Some(key) = self.find_locale_overwrite(file) { + let url_translations = self.url_translations.get(key); + page.set_locale_key(key); page.rewrite_html(); - page.rewrite_meta_tags(relative_path); + page.rewrite_meta_tags(relative_path, relative_path, &self.url_translations); page.rewrite_image_tags(); page.rewrite_assets(); - page.rewrite_anchors(); + page.rewrite_anchors(url_translations); let output_path = dest_folder.join(relative_path); page.output_file(&output_path); return; } - page.rewrite_meta_tags(relative_path); - page.rewrite_anchors(); + page.rewrite_meta_tags(relative_path, relative_path, &self.url_translations); + + let default_url_translations = self.url_translations.get(&config.default_language); + page.rewrite_anchors(default_url_translations); + + let translated_default_url = default_url_translations + .map(|t| t.get(&relative_path.to_string_lossy())) + .flatten() + .map(Into::into) + .unwrap_or_else(|| relative_path.to_owned()); let output_path = dest_folder .join(&config.default_language) - .join(relative_path); + .join(translated_default_url); page.output_file(&output_path); - self.output_redirect_file(&config.default_language, relative_path); + + self.output_redirect_file( + &config.default_language, + relative_path, + &self.url_translations, + ); self.translations.keys().for_each(|key| { + let url_translations = self.url_translations.get(key); + + let translated_url = url_translations + .map(|t| t.get(&relative_path.to_string_lossy())) + .flatten() + .map(Into::into) + .unwrap_or_else(|| relative_path.to_owned()); + page.set_locale_key(key); page.rewrite_html(); - page.rewrite_meta_tags(relative_path); + page.rewrite_meta_tags(&translated_url, relative_path, &self.url_translations); page.rewrite_image_tags(); page.rewrite_assets(); - page.rewrite_anchors(); + page.rewrite_anchors(url_translations); - let output_path = dest_folder.join(key).join(relative_path); + let output_path = dest_folder.join(key).join(translated_url); page.output_file(&output_path); }); } - pub fn output_redirect_file(&self, locale: &str, relative_path: &Path) { + pub fn output_redirect_file( + &self, + locale: &str, + relative_path: &Path, + url_translations: &BTreeMap, + ) { let config = &self.options.config; let dest_folder = &config.dest; let dest_file = dest_folder.join(relative_path); - let path = relative_path.display().to_string(); - let path = path.trim_end_matches("index.html").replace('\\', "/"); + let path = filepath_to_output_url(&relative_path.to_string_lossy()); if let Some(parent) = dest_file.parent() { create_dir_all(parent).unwrap(); @@ -113,9 +142,16 @@ impl RoseyBuilder { .chain(std::iter::once(&config.default_language)) .filter(|key| *key != locale) { + let translated_path = url_translations + .get(key) + .map(|t| t.get(&relative_path.to_string_lossy())) + .flatten() + .map(|p| filepath_to_output_url(p)) + .unwrap_or_else(|| path.clone()); + write!( alternates, - r#""# + r#""# ) .expect("Failed to output redirect - alternate link"); } @@ -137,9 +173,19 @@ impl RoseyBuilder { } } + let translated_default_url = if let Some(translated_url) = url_translations + .get(locale) + .map(|t| t.get(&relative_path.to_string_lossy())) + .flatten() + { + filepath_to_output_url(translated_url) + } else { + path + }; + let mut output = output .replace("DEFAULT_LANGUAGE", locale) - .replace("SITE_PATH", &format!("/{path}")) + .replace("SITE_PATH", &format!("/{translated_default_url}")) .replace("ALTERNATES", &alternates); if let Ok(lookup) = serde_json::to_string(&lookup) { @@ -240,12 +286,21 @@ impl<'a> RoseyPage<'a> { } pub fn process_anchors(&mut self) { + let base_url = Url::parse("https://example.com").unwrap(); + for element in self.dom.select("a[href]").unwrap() { let attributes = element.attributes.borrow(); let src = attributes.get("href").unwrap(); - let ext = src.rfind('.').map(|index| src.split_at(index + 1).1); + let Ok(parsed) = base_url.join(src) else { + continue; + }; + let ext = parsed + .path() + .rfind('.') + .map(|index| parsed.path().split_at(index + 1).1); if src.starts_with('/') + && parsed.host_str() == base_url.host_str() && matches!(ext, Some("html") | Some("htm") | None) && !self .translations @@ -259,13 +314,41 @@ impl<'a> RoseyPage<'a> { } } - pub fn rewrite_anchors(&mut self) { + pub fn rewrite_anchors(&mut self, url_translations: Option<&RoseyTranslation>) { let locale_key = self.get_locale_key(); + let base_url = Url::parse("https://example.com").unwrap(); for (original, node) in &self.anchor_tags { let element = node.as_element().unwrap(); let mut attributes = element.attributes.borrow_mut(); + + let Ok(mut parsed) = base_url.join(original) else { + continue; + }; + + if parsed.host_str() != base_url.host_str() { + continue; + } + + if let Some(urlmap) = url_translations { + let rel_url = parsed.path().trim_start_matches("/"); + + let candidate_url = if rel_url.ends_with("/") { + format!("{rel_url}index.html") + } else if rel_url.ends_with(".html") || rel_url.ends_with(".htm") { + rel_url.to_string() + } else { + format!("{rel_url}/index.html") + }; + + if let Some(modified_url) = urlmap.get(&candidate_url) { + parsed.set_path(&filepath_to_output_url(&modified_url)); + } + } + + let output = parsed.as_str().trim_start_matches(base_url.as_str()); + attributes.remove("href"); - attributes.insert("href", format!("/{locale_key}{original}")); + attributes.insert("href", format!("/{locale_key}/{output}")); } } @@ -479,11 +562,15 @@ impl<'a> RoseyPage<'a> { } } - pub fn rewrite_meta_tags(&mut self, relative_path: &Path) { + pub fn rewrite_meta_tags( + &mut self, + relative_path: &Path, + original_relative_path: &Path, + url_translations: &BTreeMap, + ) { let locale_key = self.get_locale_key(); - let path = relative_path.display().to_string(); - let path = path.trim_end_matches("index.html"); + let path = filepath_to_output_url(&relative_path.to_string_lossy()); let html_tag = self.html_tag.as_ref().unwrap(); let mut attributes = html_tag.as_element().unwrap().attributes.borrow_mut(); @@ -504,6 +591,15 @@ impl<'a> RoseyPage<'a> { .filter(|key| *key != locale_key) .enumerate() { + let translated_path = url_translations + .get(key) + .map(|t| t.get(&original_relative_path.to_string_lossy())) + .flatten() + .map(|p| filepath_to_output_url(p)) + .unwrap_or_else(|| { + filepath_to_output_url(&original_relative_path.to_string_lossy()) + }); + let mut attributes = self.link_tags[i] .as_element() .unwrap() @@ -517,9 +613,9 @@ impl<'a> RoseyPage<'a> { } if let Some(href) = attributes.get_mut("href") { - *href = format!("/{key}/{path}"); + *href = format!("/{key}/{translated_path}"); } else { - attributes.insert("href", format!("/{key}/{path}")); + attributes.insert("href", format!("/{key}/{translated_path}")); } } } diff --git a/rosey/src/runners/builder/html/utils.rs b/rosey/src/runners/builder/html/utils.rs index 8a793a6..209973c 100644 --- a/rosey/src/runners/builder/html/utils.rs +++ b/rosey/src/runners/builder/html/utils.rs @@ -11,6 +11,10 @@ use crate::RoseyTranslation; use super::get_translated_asset; +pub fn filepath_to_output_url(p: &str) -> String { + p.trim_end_matches("index.html").replace('\\', "/") +} + pub struct TranslationRewriter<'a> { result: String, images_source: &'a Path, diff --git a/rosey/src/runners/generator.rs b/rosey/src/runners/generator.rs index d6fa911..64d1403 100644 --- a/rosey/src/runners/generator.rs +++ b/rosey/src/runners/generator.rs @@ -13,6 +13,7 @@ use crate::{RoseyLocale, RoseyOptions}; pub struct RoseyGenerator { pub options: RoseyOptions, pub locale: RoseyLocale, + pub urls_locale: RoseyLocale, pub current_file: String, } @@ -22,6 +23,7 @@ impl From for RoseyGenerator { RoseyGenerator { options, locale: RoseyLocale::new(version), + urls_locale: RoseyLocale::new(version), current_file: String::default(), } } @@ -40,6 +42,7 @@ impl RoseyGenerator { walker.for_each(|file| self.process_file(file)); self.output_locale(); + self.output_urls(); } fn output_locale(&mut self) { @@ -59,6 +62,23 @@ impl RoseyGenerator { } } + fn output_urls(&mut self) { + let config = &self.options.config; + let urls_dest = &config.base_urls; + let urls_folder = urls_dest.parent().unwrap(); + create_dir_all(urls_folder).unwrap(); + let output = self.urls_locale.output(config.version); + + if let Ok(file) = File::create(urls_dest) { + let mut writer = BufWriter::new(file); + if writer.write(output.as_bytes()).is_err() { + eprintln!("Failed to write: {urls_dest:?}") + } + } else { + eprintln!("Failed to open: {urls_dest:?}") + } + } + fn process_file(&mut self, file: DirEntry) { match file.path().extension().map(|ext| ext.to_str().unwrap()) { Some("htm" | "html") => self.process_html_file(file.path()), diff --git a/rosey/src/runners/generator/html.rs b/rosey/src/runners/generator/html.rs index 41fa0d9..b9fb87f 100644 --- a/rosey/src/runners/generator/html.rs +++ b/rosey/src/runners/generator/html.rs @@ -12,6 +12,8 @@ impl RoseyGenerator { crate::inline_templates(&dom); self.current_file = String::from(file.strip_prefix(&config.source).unwrap().to_str().unwrap()); + self.urls_locale + .insert_uncounted(self.current_file.clone(), self.current_file.clone()); self.process_html_node(dom, None, None); } diff --git a/rosey/test.sh b/rosey/test.sh index 92efcbf..78d532f 100755 --- a/rosey/test.sh +++ b/rosey/test.sh @@ -2,7 +2,7 @@ cargo build if [ -z "$1" ]; then - TEST_BINARY=./target/debug/rosey npx -y humane@latest + TEST_BINARY=./target/debug/rosey npx -y humane@v0.3.12 else - TEST_BINARY=./target/debug/rosey npx -y humane@latest --name "$1" + TEST_BINARY=./target/debug/rosey npx -y humane@v0.3.12 --name "$1" fi From 9523a002dbc3f037f52199404551dd1ac5d7e719 Mon Sep 17 00:00:00 2001 From: Liam Bigelow <40188355+bglw@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:42:39 +1200 Subject: [PATCH 2/7] Quick docs --- docs/content/docs/_index.md | 1 + docs/content/docs/checks.md | 2 +- docs/content/docs/generate.md | 8 +++++ docs/content/docs/urls.md | 58 +++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/urls.md diff --git a/docs/content/docs/_index.md b/docs/content/docs/_index.md index 2fa51eb..f4ff874 100644 --- a/docs/content/docs/_index.md +++ b/docs/content/docs/_index.md @@ -93,6 +93,7 @@ _site/ index.liquid +rosey/ +>> base.json ++>> base.urls.json {{< /tree >}} This `base.json` file contains all text that needs to be translated. For the layout we tagged above, this will look like the following: diff --git a/docs/content/docs/checks.md b/docs/content/docs/checks.md index be87120..767cb97 100644 --- a/docs/content/docs/checks.md +++ b/docs/content/docs/checks.md @@ -2,7 +2,7 @@ title: "Rosey Checks File" nav_title: "Rosey Checks File" nav_section: Workflow -weight: 6 +weight: 7 --- The Rosey checks file highlights any discrepancies between your base translation file and your translated locale files, such as out of date or missing translations. diff --git a/docs/content/docs/generate.md b/docs/content/docs/generate.md index 0dd48ad..80439da 100644 --- a/docs/content/docs/generate.md +++ b/docs/content/docs/generate.md @@ -26,6 +26,14 @@ The path to generate the Rosey base locale file to. Defaults to `rosey/base.json |-----------------|--------------|------------| | `--base ` | `ROSEY_BASE` | `base` | +### Base URLs + +The path to generate the Rosey base urls file to. Defaults to `rosey/base.urls.json` + +| CLI Flag | ENV Variable | Config Key | +|----------------------|-------------------|-------------| +| `--base-urls ` | `ROSEY_BASE_URLS` | `base_urls` | + ### Separator The separator to use between Rosey namespaces when generating keys. Defaults to `:` diff --git a/docs/content/docs/urls.md b/docs/content/docs/urls.md new file mode 100644 index 0000000..5aec76a --- /dev/null +++ b/docs/content/docs/urls.md @@ -0,0 +1,58 @@ +--- +title: "Translated URLs" +nav_title: "Translated URLs" +nav_section: Workflow +weight: 6 +--- + +Rosey URL locale files can contain translated URLs for your website in a given language. + +## Creating translated URL locale files + +Creating URL locale files is not a step performed by Rosey. This part of the translation workflow is left open ended, usually integrating into an existing translation workflow for a company, or being programmatically created by transforming the input URLs. + +Rosey will look for URL locale files alongside the standard locale files. For a file at `rosey/locales/es.json`, Rosey will look for URLs at `rosey/locales/es.urls.json`. + +Locale files should be created based on the base URL file output from the [Rosey generate command](/docs/generate/). For the example base URL file: + +```json +{ + "version": 2, + "keys": { + "index.html": { + "original": "index.html" + }, + "home/index.html": { + "original": "home/index.html" + }, + "posts/hello-world.html": { + "original": "posts/hello-world.html" + } + } +} +``` + +The `rosey/locales/ja-jp.urls.json` locale file should match the structure: + +```json +{ + "index.html": { + "original": "index.html", + "value": "index.html" + }, + "home/index.html": { + "original": "home/index.html", + "value": "家/index.html" + }, + "posts/hello-world.html": { + "original": "posts/hello-world.html", + "value": "投稿/こんにちは世界.html" + } +} +``` + +Each of these keys is an object with `original` and `value` strings. The `value` string should contain the translated destination file, and will be used by Rosey when building your final multilingual site. + +The output should always include the `.html` extension. Rosey will remove any trailing `index.html` filename where able. + +All internal links to these files will be updated within the target locale. From 0b6eb0700289231096a271de6bbefbdb29449666 Mon Sep 17 00:00:00 2001 From: Liam Bigelow <40188355+bglw@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:44:13 +1200 Subject: [PATCH 3/7] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 103c17f..07f8805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ ## Unreleased +* Adds the ability to translate page URLs + * See [Docs > Translating URLs](https://rosey.app/docs/urls/) +* Fixes an issue where some internal links with query strings or hash fragments would not be rewritten + ## v2.0.5 (March 19, 2024) * Fixes an issue where `