Hyunseok
프로그래밍/개인홈페이지 Svelte - SSR + i18n을 적용해보자 aka.국제화
2023. 4. 16. 12:12

국제화.. 

이제.. 3버전에 다가와 가는 내 블로그를 만들면서 항상 생각하던 것이었지만

i18n, 풀면 internationalization.. 길긴 길다. 여하튼 한 번 적용해보고 싶었다 

 

내 낡은 기억에만 해도 보~통 국제화는

그냥 구글 번역기를 사이트에 붙여서 바로 번역하게 하는 기능.. 으로 생각하고 있었다 

 

그렇게 시대가 좋아지면서 "아 번역기 품질도 많이 올라갔나보네" 하면서 대충 넘기고 있었던 도중

19년도 LQA를 시작하게되었고 언어 현지화담당을 맡으면서 느낀 건

 

"아니 싯팔 이래서 번역 질이 올라갔구나!"

 

사람 손으로 다 하는 것이다 

그래도 게임 번역이 아니고 레이아웃 번역이니 그리 양이 많지 않아서 적용시켜 보자 

 

서론이 길다 빨리 시작해 보자 

 

svelte i18n

일단 스벨트에 i18n, 스벨트킷에 i18n이 있는데 편한 걸 써주자 

 

이번 글에서는 그냥 svelte i18n을 썼다 

 

그럼 SSR이 불가능한 것이 아니냐는 생각을 할 수도 있겠지만

그런 거 없다.

 

사실 지원 안 해도 내가 그냥 서버사이드에서 번역파일을 먼저 불러와서 data로 넘겨주면 끝이긴 한데 

이미 i18n개발자들이 예시까지 친절하게 써서 넣어줬다

 

파일 설정

 

제일 먼저 npm으로 프로젝트에 추가해 주자 

 

npm install svelte-i18n

 

그리고 프로젝트 폴더 안의 lib안에다가 폴더를 하나 만들어주자 여기서는 간단하게 i18n이라고 만들어줬다 

 

그리고 만들어진 폴더 안에 메인으로 움직일 파일 하나, 그리고 언어 셋이 들어갈 폴더를 만들어준다 

 

내 프로젝트에서는 아래와 같이 만들어졌다

알기 쉽게 i18n.ts와 locale파일을 ko.ts, jp.ts 두 개로 만들어줬다 

 

물론 언어파일을 json으로 만들어도 된다(오히려 이게 더 나은 게 아닌가 싶은데 그래도 깔맞춤 하고 싶었다)

 

그러고 나서 i18n.ts에 아래와 같이 내용을 적어준다 

 

import { browser } from '$app/environment';
import { init, register } from 'svelte-i18n';

const defaultLocale = 'ko';

register('ko', () => import('./locale/ko'));
register('jp', () => import('./locale/jp'));

init({
	fallbackLocale: defaultLocale,
	initialLocale: browser ? window.navigator.language : defaultLocale
});

공식홈에 나온 그대로 적어줬다 

 

다만 로딩시간 단축을 위해 각 언어를 레이지로드하는 방식으로 import 시켜준다

(해설은 vue router의 컴포넌트 레이지로드를 참조하면 딱 맞을 것 같다 )

 

지연된 로딩 | Vue Router

지연된 로딩 번들러를 이용하여 앱을 제작할 때 JavaScript 번들이 상당히 커져 페이지로드 시간에 영향을 줄 수 있습니다. 각 라우트의 컴포넌트를 별도의 단위로 분할하고 경로를 방문할 때 로드

v3.router.vuejs.org

 

 

언어파일은.. 공식 페이지에서는 json으로 하라 했는데 ts도 먹힌다

앞에 export default가 붙느냐 안 붙느냐의 차이다.

 

겉멋충이기에 일단 ts로 만들었고 지금 만들고 있는 프로젝트의 한국어 코드는 아래와 같다

export default {
	"page_title": "Hyunseok",
	"introduce": "소개",
	"blog": "블로그",
	"portfolio": "포트폴리오",
	"blog_all": "전체",
	"blog_dev": "개발",
	"blog_daily": "일상",
	"blog_search": "검색",
	"comment_reply": "답글",
	"comment_edit": "수정",
	"comment_delete": "삭제",
	"views" : "조회",
	"upload_finished": "업로드 완료",
	"upload_profile_img": "프로필 사진 업로드",
	"nickname":"닉네임",
	"pw":"비밀번호",
	"context":"내용",
	"submit":"확인",
	"delete_title":"댓글 삭제",
	"edit_title":"댓글 수정",
	"reply_title":"답글 등록",
	"load_not_exist":"더보기가 존재하지 않습니다",
	"loading": "불러오는 중 . . .",
	"login_title": "Hyunseok",
	"login_btn": "로그인",
	"unknownError": "알 수 없는 문제",
	"serverError":"잠시 후 다시 시도해주세요",
	"pageError":"페이지가 없거나 권한이 존재하지 않습니다",
	"goback":"이전페이지로 돌아가기",
	"github":"Github",
	"publish":"배포 사이트",
	"move_to_github":"깃허브로 이동",
	"move_to_publish":"배포 사이트로 이동"
}

 

이러면 대충 사전 설정은 다 끝났다

 

이제 아무 스벨트 파일로 들어가서 써보면 된다 

 

사용법

 

예시파일로, 좌측에서 나오는 Drawer에 들어간 간단한 i18n 예제를 가져와보았다

 

<script lang="ts">
	import { drawerStore } from '@skeletonlabs/skeleton';
	import { _ } from 'svelte-i18n';
	import menus from '../Variables/menus';
	function drawerClose(): void {
		drawerStore.close();
	}
</script>

<nav class="list-nav p-4">
	<ul>
		{#each menus as menu}
			<li>
				<a class="btn btn-sm" 
                	href={menu.href} 
                	on:click={drawerClose}
                >{$_(menu.value)}</a>
			</li>
		{/each}
	</ul>
</nav>

(이상하게 html로 해야 제대로 색이 입혀진다)

 

저런 식으로 {$_("ko.ts에 적어준 목록 이름")} 이런 식으로 적어주었다

 

나는  menu파라미터를 따로 또 떼서 파일을 관리하고 있는데 

 

저런 식으로 변수명으로도 넣을 수 있다는 것도 알아두면 좋다

 

언어 바꾸기

이건 공식홈에서 사삭 하고 넘어가는 부분이라 좀 애매할 수도 있는데 난 그냥 init으로 다시 입혀주는 방식을 택했다 

 

코드는 아래와 같다 

 

	import { _, init } from 'svelte-i18n';

	$: lang = 0;
	$: lang === 0 ? init({ fallbackLocale: 'ko' }) : init({ fallbackLocale: 'jp' });
    
    		<RadioGroup
                class="py-[0.1875rem] transition-all"
                border="0"
                active="variant-filled-primary"
                rounded="false"
                padding="px-1"
                spacing="space-y-0"
                hover="hover:variant-soft-primary"
			>
                <RadioItem 
                    class="text-xs transition-all" 
                    bind:group={lang} 
                    name="justify" 
                    value={0}
                >KO
                </RadioItem>
                <RadioItem 
                    class="text-xs transition-all" 
                    bind:group={lang} 
                    name="justify" 
                    value={1}
                >JP
                </RadioItem>
		</RadioGroup>

(지금 보니 radioitem도 따로 빼낼 수도 있겠지만.. 귀찮으니 놔두자)

 

이렇게 냅다 init을 묶어서 처리한다, 그러면 KO 누르면 한국어 셋, JP를 누르면 일본어 셋이 나온다 

 

언어가 더 늘어가면 init fallbacklocale을 string을 받아 바꾸는 함수로 빼서 할 수도 있겠지만

 

어차피 난 2개니까.. 여기서 더 늘릴 생각도 없고 여차하면 그때 만드는 것으로 하자 

 

SSR

마지막으로 SSR이 필요하다면.. 아래와 같이 하면 된다 이것 또한 공식 홈페이지에서 제안하는 내용 그대로이다

 

src최상위에 server.hook.ts를 작성한다

import type { Handle } from '@sveltejs/kit'
import { locale } from 'svelte-i18n'

export const handle: Handle = async ({ event, resolve }) => {
	const lang = event.request.headers.get('accept-language')?.split(',')[0]
	if (lang) {
		locale.set(lang)
	}
	return resolve(event)
}

 

그리고 +layout.ts에도 로딩시켜줘야 하니 아래와 같이 +layout.svelte 있는 곳에  ts파일을 작성한다

import { browser } from '$app/environment'
import '$lib/i18n' // Import to initialize. Important :)
import { locale, waitLocale } from 'svelte-i18n'
import type { LayoutLoad } from './$types'

export const load: LayoutLoad = async () => {
	if (browser) {
		locale.set(window.navigator.language)
	}
	await waitLocale()
}

참고로 import 순서가 매우 중요하니 조심하도록 하자 (이거 때문에 몇십 분 정도 까먹었다)

이러면 +layout.svelte의 slot부분에 자동으로 붙는다

 

그리고 위에서 제시한 사용 예시처럼 그대로 사용하면 끝이다 

 

 

 

마무리

이렇게 i18n을 간단하게 적용시킬 수가 있었다

 

react나 vue에서도 비교적 간단하게 가능하지만..

 

역시 svelte의 명성 어디 안 간다고 엄청 편하게 툭툭하면 바로 탁 하고 붙는다 

 

스벨트 쓰면서 느끼는 거지만..

 

왜 편한지 모르겠다

 

아니 진짜 편한데 이유는 모르겠다 다른 거 쓸 때 보다 그냥 엄청 편하다 

 

vue3 setup script와 react의 함수 컴포넌트를 합해놓은.. 그런 기묘한 느낌이 엄청나게 든다

 

또한 svelte고유의 slot이나 여러가지 기능들이 있는데

 

이건 블로그를 다 만들고 한 번 더 정독을 해봐야겠다 (유용한 게 상당히 많아 보인다 )

 

여하튼 열심히 코드 가지고 놀아보자 

 


프로그래밍/개인홈페이지의 다른 글