From bedf4501afd49d309d9f96b893ea3754920a4dbb Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Fri, 30 Jan 2026 15:12:39 -0800 Subject: [PATCH 1/7] patch attempt 1 --- plugins/resolve_md/plugin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index 080c100..bbb34fa 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -52,6 +52,13 @@ def on_post_build(self, config): docs_dir = Path(config["docs_dir"]).resolve() site_dir = Path(config["site_dir"]).resolve() + # Handle i18n: if the current language is not English, append the language code + # to the site_dir so that artifacts are written to the localized subdirectory. + # This prevents the translator build from overwriting the English artifacts in the root. + current_lang = config.get("theme", {}).get("language", "en") + if current_lang != "en" and site_dir.name != current_lang: + site_dir = site_dir / current_lang + # Snippet directory defaults to docs/.snippets snippet_dir = docs_dir / ".snippets" if not snippet_dir.exists(): From 240e5d472c90990e0593fd63193a06a8023dc247 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 2 Feb 2026 09:11:53 -0800 Subject: [PATCH 2/7] language working on per page, need to fix category files --- plugins/resolve_md/plugin.py | 41 +++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index bbb34fa..502fb0e 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -47,17 +47,16 @@ def on_post_build(self, config): self.llms_config = self.load_llms_config(project_root) snippet_cfg = self.llms_config.get("snippets", {}) self.allow_remote_snippets = snippet_cfg.get("allow_remote", True) + self.current_lang = config.get("theme", {}).get("language", "en") # Resolve docs_dir from MkDocs config (already parsed/resolved by MkDocs) docs_dir = Path(config["docs_dir"]).resolve() site_dir = Path(config["site_dir"]).resolve() - # Handle i18n: if the current language is not English, append the language code - # to the site_dir so that artifacts are written to the localized subdirectory. - # This prevents the translator build from overwriting the English artifacts in the root. - current_lang = config.get("theme", {}).get("language", "en") - if current_lang != "en" and site_dir.name != current_lang: - site_dir = site_dir / current_lang + # Separate output directories for non-English builds if they aren't already separated + # (mkdocs-static-i18n usually handles this, but resolve_md sees the raw site_dir) + if self.current_lang != "en" and site_dir.name != self.current_lang: + site_dir = site_dir / self.current_lang # Snippet directory defaults to docs/.snippets snippet_dir = docs_dir / ".snippets" @@ -178,15 +177,41 @@ def on_post_build(self, config): # ----- Helper functions ------- # File discovery and filtering per skip names/paths in llms_config.json - @staticmethod - def get_all_markdown_files(docs_dir, skip_basenames, skip_paths): + def get_all_markdown_files(self, docs_dir, skip_basenames, skip_paths): """Collect *.md|*.mdx, skipping basenames and paths that contain any skip_paths substring.""" results = [] for root, _, files in os.walk(docs_dir): if any(x in root for x in skip_paths): continue + + # --- NEW FILTERING LOGIC --- + # Determine if this folder is a localized folder (e.g. /pt/) + rel_root = os.path.relpath(root, docs_dir) + path_parts = rel_root.split(os.sep) + is_localized_folder = (len(path_parts) > 0 and path_parts[0] == 'pt') # Add other languages if needed + + # If building English, skip 'pt' folder + if self.current_lang == 'en' and is_localized_folder: + continue + + # If building Portuguese, skip root folder (non-localized files) + # You might want to adjust this if you WANT fallback content + if self.current_lang == 'pt' and not is_localized_folder: + # Optional: Allow skipping check if you want English dupes in PT build, + # but to avoid incorrect content, we often skip. + if rel_root == '.': + pass # deciding on individual files + else: + continue + # --------------------------- + for file in files: if file.endswith((".md", ".mdx")) and file not in skip_basenames: + # Additional check for root files if strictly separating + if self.current_lang == 'pt' and rel_root == '.' and not file.startswith('pt.'): + # Logic to ensure we don't pick up English root files in PT build + continue + results.append(os.path.join(root, file)) return sorted(results) From d60857aac7343276f075aefe422c9dda77ef6de3 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 2 Feb 2026 12:30:20 -0800 Subject: [PATCH 3/7] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c0501a..d23113f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "papermoon-mkdocs-plugins" -version = "0.1.0a4" +version = "0.1.0a5" description = "A collection of MkDocs plugins" readme = "README.md" requires-python = ">=3.8" From 1ea04ed5a6fff110beaf5ad5fc23a5660d99abbf Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 2 Feb 2026 13:36:09 -0800 Subject: [PATCH 4/7] supports translation array rather than hardcoding languages --- plugins/resolve_md/plugin.py | 61 +++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index 502fb0e..43a437f 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -53,11 +53,16 @@ def on_post_build(self, config): docs_dir = Path(config["docs_dir"]).resolve() site_dir = Path(config["site_dir"]).resolve() - # Separate output directories for non-English builds if they aren't already separated - # (mkdocs-static-i18n usually handles this, but resolve_md sees the raw site_dir) - if self.current_lang != "en" and site_dir.name != self.current_lang: - site_dir = site_dir / self.current_lang - + # Load i18n config + i18n_cfg = self.llms_config.get("i18n", {}) + default_locale = i18n_cfg.get("default_locale", "en") + supported_translations = i18n_cfg.get("supported_translations", []) + + # Separate output directories for supported translation builds + if self.current_lang != default_locale and self.current_lang in supported_translations: + if site_dir.name != self.current_lang: + site_dir = site_dir / self.current_lang + # Snippet directory defaults to docs/.snippets snippet_dir = docs_dir / ".snippets" if not snippet_dir.exists(): @@ -185,33 +190,47 @@ def get_all_markdown_files(self, docs_dir, skip_basenames, skip_paths): continue # --- NEW FILTERING LOGIC --- - # Determine if this folder is a localized folder (e.g. /pt/) + # Determine if this folder is a localized folder (e.g. /pt/) based on config rel_root = os.path.relpath(root, docs_dir) path_parts = rel_root.split(os.sep) - is_localized_folder = (len(path_parts) > 0 and path_parts[0] == 'pt') # Add other languages if needed - # If building English, skip 'pt' folder - if self.current_lang == 'en' and is_localized_folder: + # Load i18n config again (or pass it in arguments) + i18n_cfg = self.llms_config.get("i18n", {}) + default_locale = i18n_cfg.get("default_locale", "en") + supported_translations = i18n_cfg.get("supported_translations", []) + + # Check if current folder belongs to a supported translation + folder_lang = path_parts[0] if len(path_parts) > 0 else None + is_translation_folder = folder_lang in supported_translations + + # If building Default (English), skip translation folders + if self.current_lang == default_locale and is_translation_folder: continue - # If building Portuguese, skip root folder (non-localized files) - # You might want to adjust this if you WANT fallback content - if self.current_lang == 'pt' and not is_localized_folder: - # Optional: Allow skipping check if you want English dupes in PT build, - # but to avoid incorrect content, we often skip. - if rel_root == '.': - pass # deciding on individual files - else: + # If building Translaton (e.g. pt), skip root folder (except its own files) + # and skip OTHER translation folders (if multiple supported langs exist) + if self.current_lang in supported_translations: + # Skip if we are in a translation folder that isn't THIS language + if is_translation_folder and folder_lang != self.current_lang: continue + + # Skip if we are in the root folder (non-localized files) + if not is_translation_folder: + if rel_root == '.': + pass # handled in file loop below + else: + continue # --------------------------- for file in files: if file.endswith((".md", ".mdx")) and file not in skip_basenames: # Additional check for root files if strictly separating - if self.current_lang == 'pt' and rel_root == '.' and not file.startswith('pt.'): - # Logic to ensure we don't pick up English root files in PT build - continue - + if self.current_lang in supported_translations and rel_root == '.': + # If in root, only allow files that explicitly start with lang code (rare) + # otherwise skip all english root files + if not file.startswith(f"{self.current_lang}."): + continue + results.append(os.path.join(root, file)) return sorted(results) From 4089da67c8414b0e1f56d6b0ceffed2deda46ce8 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 2 Feb 2026 14:02:35 -0800 Subject: [PATCH 5/7] fixes resolved md url for translated AI files --- plugins/resolve_md/plugin.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index 43a437f..5d79e32 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -77,6 +77,11 @@ def on_post_build(self, config): # Determine docs_base_url for canonical URLs project_cfg = self.llms_config.get("project", {}) docs_base_url = (project_cfg.get("docs_base_url", "") or "").rstrip("/") + "/" + + # Append locale to base URL if we are building a supported translation + if self.current_lang != default_locale and self.current_lang in supported_translations: + docs_base_url = f"{docs_base_url}{self.current_lang}/" + self.docs_base_url = docs_base_url # Determine output directory for resolved markdown (inside site directory) @@ -127,10 +132,17 @@ def on_post_build(self, config): cleaned_body = self.remove_html_comments(resolved_body) if cleaned_body != resolved_body: log.debug(f"[resolve_md] stripped HTML comments in {md_path}") - # Convert path to slug and canonical URLs - rel_path = Path(md_path).relative_to(docs_dir) - rel_no_ext = str(rel_path.with_suffix("")) - slug, url = self.compute_slug_and_url(rel_no_ext, docs_base_url) + # Calculate relative path for slug generation + rel_path = os.path.relpath(md_path, docs_dir) + # If localized build, strip the language directory from rel_path + # This ensures slugs utilize 'builders/index' instead of 'pt/builders/index' + if self.current_lang != default_locale and self.current_lang in supported_translations: + parts = rel_path.split(os.sep) + if parts and parts[0] == self.current_lang: + rel_path = os.path.join(*parts[1:]) + + slug, url = self.get_markdown_slug(rel_path, self.docs_base_url) + # Calculate word count and estimated token count word_count = self.word_count(cleaned_body) token_estimate = self.estimate_tokens(cleaned_body) @@ -713,13 +725,17 @@ def finish(buf: list[str]) -> str: # Convert file path to slug, create markdown file URL @staticmethod - def compute_slug_and_url(rel_path_no_ext: str, docs_base_url: str): + def get_markdown_slug(rel_path: str, docs_base_url: str): """ - rel_path_no_ext: docs-relative path without extension, using OS separators. + rel_path: docs-relative path (with extension), using OS separators. + - Removes extension. - If endswith '/index', drop the trailing 'index' for the URL and slug base. - Slug = path segments joined by '-', lowercased. - URL = docs_base_url + route + '/' """ + # Remove extension + rel_path_no_ext, _ = os.path.splitext(rel_path) + # Normalize to forward slashes route = rel_path_no_ext.replace(os.sep, "/") if route.endswith("/index"): @@ -1026,7 +1042,6 @@ def build_site_index(self, pages: list[dict], ai_root: Path) -> None: "hash": self.sha256_text(body), "token_estimator": token_estimator, } - entry["raw_md_url"] = resolved_md_url site_index.append(entry) index_path.write_text( From 5adf016a8d1c4d77cd2941dc23149c888f2c1cb2 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Mon, 2 Feb 2026 14:31:06 -0800 Subject: [PATCH 6/7] formatting --- plugins/resolve_md/plugin.py | 85 +++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index 5d79e32..8ff7918 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -29,6 +29,7 @@ SNIPPET_RANGE_RE = re.compile(r"^(?P.+?):(?P-?\d+):(?P-?\d+)$") SNIPPET_SINGLE_RANGE_RE = re.compile(r"^(?P.+?):(?P-?\d+)$") + # Define plugin class class ResolveMDPlugin(BasePlugin): # Define value for `llms_config` in the project mkdocs.yml file @@ -59,10 +60,13 @@ def on_post_build(self, config): supported_translations = i18n_cfg.get("supported_translations", []) # Separate output directories for supported translation builds - if self.current_lang != default_locale and self.current_lang in supported_translations: + if ( + self.current_lang != default_locale + and self.current_lang in supported_translations + ): if site_dir.name != self.current_lang: - site_dir = site_dir / self.current_lang - + site_dir = site_dir / self.current_lang + # Snippet directory defaults to docs/.snippets snippet_dir = docs_dir / ".snippets" if not snippet_dir.exists(): @@ -77,11 +81,14 @@ def on_post_build(self, config): # Determine docs_base_url for canonical URLs project_cfg = self.llms_config.get("project", {}) docs_base_url = (project_cfg.get("docs_base_url", "") or "").rstrip("/") + "/" - + # Append locale to base URL if we are building a supported translation - if self.current_lang != default_locale and self.current_lang in supported_translations: + if ( + self.current_lang != default_locale + and self.current_lang in supported_translations + ): docs_base_url = f"{docs_base_url}{self.current_lang}/" - + self.docs_base_url = docs_base_url # Determine output directory for resolved markdown (inside site directory) @@ -136,7 +143,10 @@ def on_post_build(self, config): rel_path = os.path.relpath(md_path, docs_dir) # If localized build, strip the language directory from rel_path # This ensures slugs utilize 'builders/index' instead of 'pt/builders/index' - if self.current_lang != default_locale and self.current_lang in supported_translations: + if ( + self.current_lang != default_locale + and self.current_lang in supported_translations + ): parts = rel_path.split(os.sep) if parts and parts[0] == self.current_lang: rel_path = os.path.join(*parts[1:]) @@ -200,12 +210,12 @@ def get_all_markdown_files(self, docs_dir, skip_basenames, skip_paths): for root, _, files in os.walk(docs_dir): if any(x in root for x in skip_paths): continue - + # --- NEW FILTERING LOGIC --- # Determine if this folder is a localized folder (e.g. /pt/) based on config rel_root = os.path.relpath(root, docs_dir) path_parts = rel_root.split(os.sep) - + # Load i18n config again (or pass it in arguments) i18n_cfg = self.llms_config.get("i18n", {}) default_locale = i18n_cfg.get("default_locale", "en") @@ -218,31 +228,31 @@ def get_all_markdown_files(self, docs_dir, skip_basenames, skip_paths): # If building Default (English), skip translation folders if self.current_lang == default_locale and is_translation_folder: continue - + # If building Translaton (e.g. pt), skip root folder (except its own files) # and skip OTHER translation folders (if multiple supported langs exist) if self.current_lang in supported_translations: - # Skip if we are in a translation folder that isn't THIS language - if is_translation_folder and folder_lang != self.current_lang: - continue - - # Skip if we are in the root folder (non-localized files) - if not is_translation_folder: - if rel_root == '.': - pass # handled in file loop below - else: - continue + # Skip if we are in a translation folder that isn't THIS language + if is_translation_folder and folder_lang != self.current_lang: + continue + + # Skip if we are in the root folder (non-localized files) + if not is_translation_folder: + if rel_root == ".": + pass # handled in file loop below + else: + continue # --------------------------- for file in files: if file.endswith((".md", ".mdx")) and file not in skip_basenames: # Additional check for root files if strictly separating - if self.current_lang in supported_translations and rel_root == '.': - # If in root, only allow files that explicitly start with lang code (rare) - # otherwise skip all english root files - if not file.startswith(f"{self.current_lang}."): - continue - + if self.current_lang in supported_translations and rel_root == ".": + # If in root, only allow files that explicitly start with lang code (rare) + # otherwise skip all english root files + if not file.startswith(f"{self.current_lang}."): + continue + results.append(os.path.join(root, file)) return sorted(results) @@ -446,7 +456,9 @@ def apply_snippet_selectors( """Apply section or line slicing to snippet content.""" selected = content if section: - section_content = self.extract_snippet_section(selected, section, snippet_ref) + section_content = self.extract_snippet_section( + selected, section, snippet_ref + ) if section_content is None: return None selected = section_content @@ -480,7 +492,9 @@ def _normalize_line_index(index: int | None, total: int, default: int) -> int: return value @staticmethod - def extract_snippet_section(content: str, section: str, snippet_ref: str) -> str | None: + def extract_snippet_section( + content: str, section: str, snippet_ref: str + ) -> str | None: """Return the text between --8<-- [start:section] and [end:section] markers.""" target = section.strip().lower() if not target: @@ -519,7 +533,9 @@ def fetch_local_snippet(self, snippet_ref: str, snippet_directory: Path) -> str: try: absolute_snippet_path.relative_to(snippet_directory_root) except ValueError: - log.warning(f"[resolve_md] invalid local snippet path (outside snippet directory): {snippet_ref}") + log.warning( + f"[resolve_md] invalid local snippet path (outside snippet directory): {snippet_ref}" + ) return f"" if not absolute_snippet_path.exists(): log.warning(f"[resolve_md] missing local snippet {snippet_ref}") @@ -773,6 +789,7 @@ def get_ai_output_dir(self, base_dir: Path) -> Path: ) from None return ai_path + def reset_directory(self, output_dir: Path) -> None: """Remove existing artifacts before writing fresh files.""" output_dir.mkdir(parents=True, exist_ok=True) @@ -898,7 +915,11 @@ def write_category_bundle( lines.append("\n---\n") title = page.get("title") or page["slug"] lines.append(f"Page Title: {title}\n") - resolved_url = f"{resolved_base}/{page['slug']}.md" if resolved_base else f"{page['slug']}.md" + resolved_url = ( + f"{resolved_base}/{page['slug']}.md" + if resolved_base + else f"{page['slug']}.md" + ) lines.append(f"- Resolved Markdown: {resolved_url}") html_url = page.get("url") if html_url: @@ -1027,7 +1048,9 @@ def build_site_index(self, pages: list[dict], ai_root: Path) -> None: } resolved_md_url = ( - f"{resolved_base}/{page['slug']}.md" if resolved_base else f"{page['slug']}.md" + f"{resolved_base}/{page['slug']}.md" + if resolved_base + else f"{page['slug']}.md" ) entry = { "id": page["slug"], From 44f96587bd129b273b6fe3645b0aadd4fc5e8f86 Mon Sep 17 00:00:00 2001 From: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:32:13 -0800 Subject: [PATCH 7/7] Update plugins/resolve_md/plugin.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/resolve_md/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/resolve_md/plugin.py b/plugins/resolve_md/plugin.py index 8ff7918..91ed5dd 100644 --- a/plugins/resolve_md/plugin.py +++ b/plugins/resolve_md/plugin.py @@ -229,7 +229,7 @@ def get_all_markdown_files(self, docs_dir, skip_basenames, skip_paths): if self.current_lang == default_locale and is_translation_folder: continue - # If building Translaton (e.g. pt), skip root folder (except its own files) + # If building Translation (e.g. pt), skip root folder (except its own files) # and skip OTHER translation folders (if multiple supported langs exist) if self.current_lang in supported_translations: # Skip if we are in a translation folder that isn't THIS language