> ## Content Index
> Fetch the complete content index at: https://sanghunkang.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Multilingual Ghost CMS Blog: 9. Language Switching on Author and Post Pages
- URL: https://sanghunkang.com/multilingual-ghost-cms-9-author-and-post-language-switch-en/
- Published: 2026-09-26T14:38:10.000Z
- Updated: 2026-09-26T14:41:35.000Z
- Author: Sanghun Kang
- Tags: Ghost, #en, #multi

There were still two issues left in the existing theme.

1. On Tag pages, only posts in the selected language are shown, but on Author pages, both English and Korean posts appear together.
2. Even if a translated post exists, changing the language in the Language selector takes you not to that post, but to the main page of the selected language.

In 0.0.9, I updated the Author page to work the same way as the existing Tag page, and I changed `#multi` posts so they go directly to the translated post.

![In 0.0.8, the Author page shows both English and Korean posts together](https://sanghunkang.com/content/images/2026/08/0.0.9-author-before-0.0.8.png)

This is the `/author/sanghun/` screen in theme 0.0.8\. The selector is set to English, but English and Korean posts still appear together in one list.

[0.0.9 · Author filtering and post language switching](https://github.com/sanghunka/ghost-multilingual-theme/commit/60adab1?ref=sanghunkang.com)

## Adding multilingual handling to the Author page

The current Author page uses this single URL.

```text
/author/sanghun/

```

When Ghost renders this page, it fetches all of the author's posts without separating them by language. Because the language stored in the browser's `localStorage` cannot be known on the server, the Handlebars template also cannot prefetch English and Korean posts separately.

I solved this issue the same way as the existing Tag page.

1. On the server, all posts are rendered.
2. Each post card includes all tags for that post.
3. In the browser, the currently selected language is checked.
4. Only cards with matching `#en` or `#ko` are shown.

### author.hbs

The `data-tags` attribute of each post card includes both public tags and internal tags.

```handlebars
{{#foreach posts}}
    <div class="post-card" data-tags='[{{#foreach tags visibility="all"}}"{{name}}"{{#unless @last}},{{/unless}}{{/foreach}}]'>
        {{> "post-card"}}
    </div>
{{/foreach}}

```

The important part here is `visibility="all"`. Since `#en`, `#ko`, and `#multi` are internal tags, they would not be included in the `{{#foreach tags}}` result without this option.

### assets/js/util.js

I extracted the shared logic into `filterArchivePostsByLanguage()` so the Tag page and Author page can use the same filtering function.

```javascript
function filterArchivePostsByLanguage() {
  const supportedLanguages = ["en", "ko"];
  const selectedLanguage = getSelectedLanguage();
  const postCards = document.querySelectorAll("div.post-card[data-tags]");
  let visibleCount = 0;

  postCards.forEach((card) => {
    let tagNames = [];

    try {
      tagNames = JSON.parse(card.getAttribute("data-tags") || "[]");
    } catch (error) {
      tagNames = [];
    }

    const cardLanguage = supportedLanguages.find((language) =>
      tagNames.includes(`#${language}`)
    );

    if (cardLanguage !== selectedLanguage) {
      card.style.display = "none";
      return;
    }

    visibleCount += 1;
    setTimeout(() => {
      card.style.opacity = 1;
      card.querySelectorAll(".post-card-excerpt").forEach((excerpt) => {
        excerpt.style.opacity = 1;
      });
    }, 10);
  });

  return visibleCount;
}

```

In `author.hbs` and `tag.hbs`, this function is called after the DOM is ready.

```javascript
document.addEventListener('DOMContentLoaded', function() {
    filterArchivePostsByLanguage();
});

```

Now on the Author page as well, if the Language selector is set to English, only `#en` posts are shown, and if it is set to Korean, only `#ko` posts are shown.

![In 0.0.9, the Author page shows only English posts](https://sanghunkang.com/content/images/2026/08/0.0.9-author-after-en.png)

This is the result of selecting English on the same `/author/sanghun/` URL. Only `#en` posts remain.

![In 0.0.9, the Author page shows only Korean posts](https://sanghunkang.com/content/images/2026/08/0.0.9-author-after-ko.png)

If you select Korean without changing the URL, only `#ko` posts remain.

## Moving to the translated post from a `#multi` post

In this blog, posts that are translations of each other follow these rules.

- The slug of an English post ends with `-en`.
- The slug of a Korean post ends with `-ko`.
- Both posts have the `#multi` internal tag.

For example, the following two posts are translations of each other.

```text
/example-post-en/
/ko/example-post-ko/

```

In 0.0.8, I used these rules to generate the `hreflang` URL automatically. In 0.0.9, I extended that so the Language selector can also use the URL that was already computed.

### partials/hreflang-multi.hbs

I store the current page's content type, whether it is a `#multi` post, the current language, and the language-specific URLs in a global object.

```javascript
window.casperMultiLanguageContext = {
    contentType: "{{contentType}}",
    isMulti: isMulti,
    currentLanguage: lang,
    urls: urls
};

```

If it is a `#multi` post, `urls` has the following form.

```javascript
{
  en: "https://example.com/example-post-en/",
  ko: "https://example.com/ko/example-post-ko/"
}

```

### default.hbs

When the Language selector value changes, it checks whether the current page is a `#multi` post. If the conditions match, it immediately moves to the post URL for the selected language.

```javascript
if (
    contentContext &&
    contentContext.contentType === 'post' &&
    contentContext.isMulti &&
    contentContext.urls[selectedLanguage]
) {
    window.location.href = contentContext.urls[selectedLanguage];
} else {
    window.location.href = selectedLanguage === 'ko' ? '/ko/' : '/';
}

```

The final behavior is as follows.

- `#multi` post: move to the version of that post in the other language
- Post without `#multi`: move to the main page of the selected language
- Page: move to the main page of the selected language
- Tag or Author page: reload the current URL and show only posts in the selected language
- Any other screen: move to the main page of the selected language

![Korean #multi post before switching](https://sanghunkang.com/content/images/2026/08/0.0.9-post-switch-before-ko.png)

On the Korean translated post, the selector points to Korean.

![Result after selecting English and moving to the corresponding English post](https://sanghunkang.com/content/images/2026/08/0.0.9-post-switch-after-en.png)

If you switch the selector to English, it goes directly not to the English main page but to the corresponding `/orca-codex-threads-chatgpt-en/` post.

## Limitations of Author page filtering

Language filtering on the Author page works by hiding cards that have already been rendered in the browser. As a result, the pagination calculated by Ghost is still based on the full set of posts before language filtering.

As the number of posts grows, the following issues may appear.

- The number of posts actually visible on a page may decrease.
- Some pages may contain very few posts in the selected language.
- The total number of pages may not match the number of posts in the selected language.

On the current blog, this causes no practical problem, so I chose the same approach as the existing Tag page. If accurate language-specific pagination becomes necessary later, it can be changed to a method that adds language-specific Channels without modifying Ghost Core.

[Proposal for Language-Specific Author Channels](https://github.com/sanghunka/ghost-multilingual-theme/blob/feature/0.0.9-author-language-switch/AUTHOR%5FLANGUAGE%5FCHANNEL%5FPROPOSAL.md?ref=sanghunkang.com)

## Result

In 0.0.9, the behavior of the Language selector was changed to fit the context of each screen.

On the Author page, you can now view only posts in the selected language just like on the existing Tag page. On translated `#multi` posts, it goes directly to the corresponding post without passing through the main page.

I did not modify Ghost Core or the database. Since this change was also handled entirely within the theme, it can be applied as-is in managed hosting environments like ghost.io.