Skip to content

Fix: Fix URL Anchor Hash Inconsistency When Switching Languages #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

clicelee
Copy link
Contributor

@clicelee clicelee commented Feb 16, 2025

📝 Key Changes

This PR ensures that anchor hash values remain consistent across different languages. Previously, switching languages would not update the hash, causing incorrect navigation. By implementing a slugify function and using a titleMap based on English headers, it now generates unified anchor values automatically, preventing unwanted page jumps.

Changed file: .vitepress/config.mts

Problem

In the Frontend Fundamentals, when navigating to a specific section, the section title is appended as a hash value in the URL.

For example:
✅ Korean URL:
https://frontend-fundamentals.com/code/examples/submit-button.html#👃-코드-냄새-맡아보기
However, when switching to another language, the hash value does not change, which results in incorrect navigation.

❌ Incorrect behavior when switching languages:
https://frontend-fundamentals.com/ja/code/examples/submit-button.html#👃-코드-냄새-맡아보기
Since the hash remains the same across all languages, the browser does not navigate to the correct section and instead moves to the top of the page.


Solution Written in VitePress

According to VitePress documentation, we can unify the anchor hash values across different languages by explicitly specifying the anchor name in the markdown headers:

## 코드 예시 {#code-example}
## 代码示例 {#code-example}
## Code Example {#code-example}

This ensures that all languages use the same #code-example hash, leading to consistent navigation:
...com/en/#code-example
...com/ko/#code-example

However manually defining {#anchor-name} for every header in a multilingual documentation is tedious and error-prone, I automated the process by using a slugify function.


Solution using slugify and titleMap

  • The anchor values are based on English headers (titleMap follows English headers as the default).
  • If the current header is in another language, it is mapped to its corresponding English anchor using titleMap.
  • If a header is slightly different but should share the same anchor (e.g., Code Example 1: LoginStartPage and Code Example), I explicitly define the mapping in titleMap.
  • Any extra symbols (such as emojis) are removed to ensure clean and consistent anchor values.
const titleMap: Record<string, string> = {
    "Code Example": "code-example",
    "코드 예시": "code-example",
    代码示例: "code-example",
    コード例: "code-example",
  }

Implementation Summary

  • Step 1: Remove emojis and normalize the string.
  • Step 2: Check for an exact match in titleMap.
  • Step 3: If no exact match, find the closest partial match.
  • Step 4: Generate a slug from the header text.
  anchor: {
      slugify: (str) => {
        // 1. Remove emojis
        let cleanedStr = str
          .normalize("NFKC")
          .replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu, "")
          .replace(/\uFE0F/g, "")
          .trim();

        // 2. Check for an exact match in titleMap
        if (titleMap[cleanedStr]) {
          return titleMap[cleanedStr];
        }

        // 3. Find the longest partial match in titleMap
        let matchedKey = Object.keys(titleMap)
          .filter((key) => cleanedStr.includes(key))
          .sort((a, b) => b.length - a.length)[0];

        if (matchedKey) {
          return titleMap[matchedKey];
        }
        // 4. Apply general slug conversion
        let slug = cleanedStr
          .trim()
          .toLowerCase()
          .replace(/[^\p{L}\p{N}\s-]/gu, "")
          .replace(/\s+/g, "-");

        return slug;
      }

💡 Additional Notes

  • Future headers should be added to titleMap to maintain consistency.

🖼️ Before and After Comparison

Before
When switching to another language, the hash value does not change, which results in incorrect navigation.
Since the hash remains the same across all languages, the browser does not navigate to the correct section and instead moves to the top of the page.

BEFORE.mov

After
When switching between languages, users can stay on the correct section instead of being sent to the top of the page.

AFTER.mov

Copy link

vercel bot commented Feb 16, 2025

@clicelee is attempting to deploy a commit to the Toss Team on Vercel.

A member of the Team first needs to authorize it.

@clicelee clicelee changed the title Fix: Fix Anchor Hash Inconsistency When Switching Languages Fix: Fix URL Anchor Hash Inconsistency When Switching Languages Feb 16, 2025
Copy link
Collaborator

@milooy milooy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @clicelee, thanks for the PR! I really appreciate you finding and fixing this issue. Sorry for the late comment.

I have a couple of thoughts:

  1. This solution requires adding every possible header text to the titleMap. As our documentation grows and changes, managing that could become a hassle. What do you think about that?
  2. Also, is it really necessary to have anchors work across different languages? The current version keeps the anchor static for a single language, and while the new approach fixes the inconsistency, it relies on a fixed titleMap, which might bring its own challenges.

I'd love to hear your thoughts on these trade-offs. Thanks again for your work on this!

@clicelee
Copy link
Contributor Author

clicelee commented Apr 30, 2025

Hi @milooy,

First, I'm really sorry for the late reply—it's been almost two months since your review. I wanted to provide a thoughtful answer and did extensive research to ensure my response addresses your concerns clearly.

🔑 Summary of the Original Issue

The current implementation has a critical UX issue:
When switching languages, the URL anchor hash (e.g., #👃-코드-냄새-맡아보기) does not update to match the target language's heading, resulting in incorrect navigation—users end up at the top of the page instead of the desired section.

Initially, I noticed this issue while contributing translations myself: I frequently switched between English and Korean documentation whenever the English wording felt unclear. At that moment, I experienced this navigation issue personally and found it frustrating. Realizing this might just be my personal perspective, I decided to expand my viewpoint and conduct thorough research to verify if this issue impacts a broader audience.


📌 Why language-agnostic anchors matter

After this in-depth research, I've concluded it's indeed crucial for anchors to remain consistent across languages. Here are the main reasons supporting broader user impact:

Aspect Practical Impact Supporting Evidence
Collaboration & Sharing URLs copied from one language reliably point to the exact same section in another language, enabling seamless sharing across global teams and communities. https://vitepress.dev/ko/guide/markdown#header-anchorshttps://vitepress.dev/guide/markdown#header-anchors
docusaurus
Accessibility (WCAG) WCAG 2.1 clearly mandates consistent identification for identical functions across pages (SC 3.2.4). WCAG SC 3.2.4
SEO & Analytics Fragment URLs (#hash) are treated as distinct by social-share tools, web analytics, and SEO crawlers. Inconsistent anchors fragment page analytics and negatively impact SEO. Simo Ahava: URL Fragments & Analytics, Parse.ly Docs
URI Stability The fundamental web principle "Cool URIs don't change" emphasizes stable fragment identifiers as part of good web architecture. Translating anchor IDs violates this core principle. Tim Berners-Lee (W3C)
RFC alignment While RFCs do not directly mention multilingual anchors, core RFCs related to URI syntax and HTML (e.g., RFC 3986 §3.5, RFC 1866 §7.4 state that fragment identifiers must match exactly, case-sensitive, and retain stable semantics. Changing anchors during translation violates this assumption and breaks both URI integrity and HTML anchor resolution. RFC 3986, 7320, 8820, and HTML 2.0 spec

🚧 Addressing your concern: titleMap management complexity

You're absolutely right—manually updating a titleMap may become burdensome as documentation scales. Here are robust, automated alternatives I identified:

Solution How it Works Benefit Real-world usage example
CI-based Guard A post-build script checks for missing/duplicate anchors and fails CI accordingly. Automated detection, immediate feedback. Docusaurus onBrokenAnchors, VuePress plugin-links-check
Central i18n Keys Manage heading texts via stable JSON keys (like locales/en.json). Translators only update values, not keys. Completely removes manual anchor management. Docusaurus i18n Tutorial
Manual ID tags Explicitly define IDs in Markdown (## Code Example {#code-example}). Simple, explicit, suitable for smaller docs. VitePress Official Docs

Among these, the Central i18n Key method seems particularly suitable long-term, aligning well with our current VitePress documentation infrastructure and future scalability needs.


💡 Recommended Next Steps (for scalability)

  1. Merge the current PR (slugify + titleMap) as a practical fix to resolve the immediate issue.
  2. Plan a phased transition towards a central i18n JSON key approach, significantly reducing future maintenance overhead.
  3. Establish clear documentation and translation guidelines to prevent anchor-related issues in future translations.

Bottom Line

Consistent, language-agnostic anchors significantly improve UX, accessibility, SEO, and link reliability.
Although the current PR addresses the immediate concern, implementing a structured solution (such as the central i18n key method) ensures long-term scalability while staying aligned with WCAG, URI best practices, and RFC standards for identifier stability.

Also, beyond this specific fix, I truly hope that this Frontend Fundamentals guide continues to grow globally and be used widely.
To support that, I believe solving this multilingual anchor issue is essential for maintaining a high-quality, consistent experience across languages.

Thanks again for your feedback—I'm happy to revise further if needed!

clicelee added 2 commits May 1, 2025 18:42
- Relocated the titleMap to the new directory structure to avoid conflicts
Relocated the titleMap logic to the new directory structure in code-quality/.vitepress/config.mts.
This implementation ensures consistent anchor IDs across different language versions
by mapping headers in Korean, Japanese, and Chinese to standardized English anchors.

Key features:
- Direct mapping of non-English headers to English anchor IDs
- Special character and emoji handling
- Partial matching support for consistent navigation
- Seamless cross-language document linking
@milooy
Copy link
Collaborator

milooy commented Jun 9, 2025

@clicelee Thank you for the detailed reply. I’ll review it and get back to you by the end of this week!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants