고스트 CMS 다국어 블로그: 4. tag 다국어 처리

고스트 CMS 다국어 블로그: 4. tag 다국어 처리

news태그 페이지로 접속해보자.

https://{host}/tag/news/

해당 태그 페이지를 조회해보면 한국어 포스트(#ko: ko news1, ko news2), 영어 포스트(#en: en news1, en news2), 언어태그가 지정되지 않은 포스트(Comming Soon)가 전부 보여지고 있다.

routes.yaml을 다시 봐보자

routes:

collections:
  /:
    filter: "tag:en"
    permalink: /{slug}/
    template: index-en
  /ko/:
    filter: "tag:ko"
    permalink: /ko/{slug}/
    template: index-ko

taxonomies:
  tag: /tag/{slug}/
  author: /author/{slug}/

routes.yaml

영어와 한국어 포스트​​​ 경로를 아예 다르게 설정했다. (/, /ko/)

그러나 tag는 그렇게 할 수 없다. tag는 여러개의 경로를 설정할 수 없으며 현재 /tag/{slug}/ 로 설정되어 있다. 즉, 하나의 화면에서 다국어 처리를 해야한다.

다음과 같이 tag페이지를 구현해서 다국어 처리를 해보자.

  • Language selector에 선택된 언어태그를 가지는 포스트만 노출시키기
  • Language selector에서 언어를 변경하면 보여지는 포스트 변경하기
0.0.4 · sanghunka/ghost-multilingual-theme@d603058
Multilingual tag page

Language selector 작동 방식 변경

현재는 Language selector값을 변경시 / 또는 /ko/로 리다이렉트 된다.

tag페이지에서는 Language selector값을 변경시, 리다이렉트 되지 않고 해당 언어태그를 가지는 포스트만 노출되도록 하고 바꾸어 보겠다.

Language selector가 tag페이지와 tag가 아닌 다른페이지의 경우에 따라 다르게 작동하도록 default.hbs를 수정하자.

{{ghost_foot}}

<script>
    function initializeLanguageSelector() {
        var selector = document.getElementById('languageSelector');
        var currentUrl = window.location.href;
        
        if (!currentUrl.includes('/tag/')) {
            var urlLanguage = currentUrl.includes('/ko/') ? 'ko' : 'en';
            localStorage.setItem('selectedLanguage', urlLanguage);
            selector.value = urlLanguage;
        } else {
            selector.value = localStorage.getItem('selectedLanguage') || 'en';
        }

        selector.style.display = 'block';

        selector.addEventListener('change', function() {
            localStorage.setItem('selectedLanguage', this.value);
            if (!currentUrl.includes('/tag/')) {
                window.location.href = this.value === 'ko' ? '/ko/' : '/';
            }
            else {
                window.location = window.location.href;
            }
        });
    }
    
    initializeLanguageSelector();
</script>
  • 8행과 20행에서 tag페이지인지 아닌지 조건을 확인한다.
    • if (!currentUrl.includes('/tag/'))
  • 13행
    • 만약 tag페이지라면 url을 확인하지 않고 현재 localStorage에 저장된 selectedLanguage값을 그대로 가져와 Language selector의 값으로 설정해준다. 만약 해당 값이 없다면(바로 url로 접속한 경우) 기본값인 en으로 설정해준다.
    • selector.value = localStorage.getItem('selectedLanguage') || 'en';
  • 24행
    • 만약 tag페이지라면, /ko/나 /로 리다이렉트하지 않고 그냥 새로고침을 한다.
    • window.location = window.location.href;

새로고침을 하는 이유는 해당 언어에 맞는 포스트를 보여주기 위해서이다. 여기까지 적용을 하면 tag페이지에서 Language selector값을 변경한다고 해서 /ko나 /로 리다이렉트 되진 않는다. 그러나 해당 언어에 맞는 포스트가 보이진 않는다. 이를 위해서 tag.hbs를 수정해야 한다.

tag.hbs 수정

수정된 tag.hbs 코드는 아래 링크에서 확인할 수 있다.

ghost-multilingual-theme/multi/tag.hbs at d6030585a2b1a0ece111f6dbb7b434a147ba4005 · sanghunka/ghost-multilingual-theme
Contribute to sanghunka/ghost-multilingual-theme development by creating an account on GitHub.

아이디어는 다음과 같다.

  • 템플릿은 서버사이드에서 처리된다. 프론트엔드에서 가지고 있는 localStorage값을 활용해 특정 언어의 포스트만 가져올 수 없다.
  • 일단 다 가져온 다음에 거르는 방식을 활용해야 한다.
  • tag페이지에서 post는 <div class="post-card"> 형태이다. 이 div에 언어정보도 함께 넣어준 뒤 프론트엔드에서 필터링을 하자.

그러나 문제는 해당 포스트가 어떤 언어태그를 가지고 있는지 템플릿단에서 알 수 있는 방법이 없다. 이를 해결하기위해 첫번째 포스트에서 정한 이 튜토리얼의 규칙이 있다.

💡
두번째 태그를 언어태그의 위치로 정하자
{{#foreach posts}}
    <div class="post-card" data-language="{{tags.[1].name}}">
        {{!-- The tag below includes the markup for each post - partials/post-card.hbs --}}
        {{> "post-card"}}
    </div>
{{/foreach}}

두번째 태그(tags.[1].name)를 가져오면 해당 포스트의 언어를 템플릿단에서 넘겨줄 수 있게 된다.

이제 프론트엔드 부분을 보자. 먼저 모든 post-card를 안보이게 처리해야 한다. opacity를 0으로 만들자. language selector에서 언어 변경시 포스트가 부드럽게 나나는 효과를 주기 위해 5번행을 추가했다.

<style>
    .post-card-excerpt,
    div.post-card {
        opacity: 0; /* Initially set all post cards to be transparent */
        transition: opacity 0.5s ease; /* Transition for smooth display */
    }
</style>

조건에 맞는 포스트는 opacity를 1로 만들어서 보이게 하자

<script>
document.addEventListener('DOMContentLoaded', function() {
    const selectedLanguage = localStorage.getItem('selectedLanguage') || 'en';  // Read language setting from local storage or use default 'en'
    const postCards = document.querySelectorAll('div.post-card[data-language]');
    let visibleCount = 0;
    postCards.forEach(card => {
        // Remove '#' character and update language setting
        const language = card.getAttribute('data-language');
        const cleanedLanguage = language.replace('#', '');
        card.setAttribute('data-language', cleanedLanguage);
        // Check if it matches the selected language, then show or hide
        if (cleanedLanguage !== selectedLanguage) {
            card.style.display = 'none';
        } else {
            visibleCount++;  // Count the number of posts that match the selected language
            setTimeout(() => {  // Use setTimeout for a smooth display
                card.style.opacity = 1;  // Set opacity to 1 to make the element visible
            }, 10);
        }
    });
    // Dynamically update text
    const collectionText = document.querySelector('.post-card-excerpt');
    if (collectionText) {
        const text = visibleCount === 0 ? 'zero posts' :
                    visibleCount === 1 ? '1 post' :
                    `${visibleCount} posts`;  // Set text based on the count
        collectionText.innerHTML = `A collection of ${text}`;  // Set text in HTML
        setTimeout(() => {  // Use setTimeout for a smooth display
            collectionText.style.opacity = 1;  // Set opacity to 1 to make the element visible
        }, 10);
    }
});
</script>
  • localStorage에 저장된 selectedLanguage값을 가져오며 해당 값이 없는 경우 기본깂인 'en'을 쓴다.
  • 언어태그는 #en 또는 #ko이다. selectedLangugae값은 en또는 ko이다. 직접적인 비교를 위해 data-language attribute에서 #을 전부 제외해주자.
  • 조건에 맞지 않으면 display = 'none', 조건에 맞으면 opacity = 1로 변경해서 포스트를 노출시킨다
  • 이 과정을 처리하며 visibleCount값을 카운트해서 몇 개의 포스트가 있는지 화면에 나타내준다.

결과

원하는대로 잘 구현되었다. 더 간결한 방법으로 CSS처리를 할 수 있을거 같은데 내 지식으로는 여기까지가 최선인듯 하다.

앞으로

  • About, Author페이지 다국어처리
  • 좌측상단 로고 클릭시 무조건 /로 리다이렉트 됨

아직 처리할 부분이 많이 있다. 그러나 사용성에 크게 영향을 미치지 않는 부분이라 차근차근 수정해보려고 한다.