Scully에 ngx-translate 적용 시 rehydration 방지

Adam Kim·2025년 10월 10일
0

angular

목록 보기
46/88

과제

  1. scully에 ngx-translate 적용하기
  2. 실행 후 언어를 변경하고 refresh 했을 때 rehydration 현상 확인
  3. 처음부터 변경된 언어로 작성된 static html을 화면에 노출하기

계획

  1. LocalStorage를 활용하여 변경된 언어를 저장합니다.
  2. 리로드 시 페이지 로딩 첫 시점부터 변경된 언어가 적용된 html 이 보여야 합니다.

DEMO 작성

다국어 적용 (ngx-translate 적용)

기존 코드에 ngx-translate을 설치합니다. 다국어 파일을 httploader를 통해 불러오므로 함께 설치합니다.

npm i @ngx-translate/core @ngx-translate/http-loader

app.config에서 ngx-translate 사용을 설정합니다. 이 때, defaultLanguage를 localstorage에서 가져온 값으로 설정합니다.

...
// src/app/app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { routes } from './app.routes';

// ngx-translate의 HttpLoader를 위한 팩토리 함수
export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

// LocalStorage 접근은 브라우저 환경에서만 가능하도록 처리
const defaultLanguage = (typeof window !== 'undefined' && window.localStorage.getItem('lang')) || 'ko';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(), // HttpClientModule 대신 사용
    importProvidersFrom(
      TranslateModule.forRoot({
        defaultLanguage: defaultLanguage,
        loader: {
          provide: TranslateLoader,
          useFactory: createTranslateLoader,
          deps: [HttpClient],
        },
      })
    ),
  ],
};

assets/i18n에 다국어를 위해 ko.json과 en.json을 만들고 값을 넣습니다. (한국어를 길게 넣은 이유는 테스트 결과 확인 시 잘 보이도록 하기 위함입니다.)

// ko.json
{
  "lang": "한국어한국어한국어한국어한국어한국어"
}
// en.json
{
  "lang": "eng"
}

template에 언어 변경 버튼 및 텍스트를 배치합니다.

// src/app/app.component.ts
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { TranslateModule, TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, TranslateModule], // TranslateModule과 TranslatePipe를 위해 필요
  template: `
    <button (click)="changeLanguage('ko')">KO</button>
    <button (click)="changeLanguage('en')">EN</button>
    
    <p>{{ 'lang' | translate }}</p>

    <router-outlet></router-outlet>
  `,
})
export class AppComponent {
  private translateService = inject(TranslateService);

  constructor() {
    // 컴포넌트 초기화 시점에 기본 언어 설정
    const lang = localStorage.getItem('lang') ?? 'ko';
    this.translateService.setDefaultLang(lang);
    this.translateService.use(lang);
  }

  changeLanguage(lang: string): void {
    localStorage.setItem('lang', lang);
    this.translateService.use(lang);
  }
}

이 상태로 scully를 실행하여 영문으로 변경한 뒤 새로고침을 해보면, static으로 생성된 국문 html 파일이 먼저 생성된 후 변경된 언어가 적용됨을 확인할 수 있습니다. 즉, 목적에서 짚은 현상이 발견 되는 것을 확인할 수 있습니다.

해결

ngx-translate-router 적용

위의 목적을 달성해줄 라이브러리를 설치합니다.

npm i @gilsdav/ngx-translate-router

1. app.config.ts에 LocalizeRouterModule 설정 추가

app.config.ts에 LocalizeRouterModule을 선언합니다. 이 때, 반드시 TranslateService와 RouterModule 이 먼저 선언 되어야 합니다.

// src/app/app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { LocalizeRouterModule, LocalizeRouterSettings, LocalizeParser } from '@gilsdav/ngx-translate-router';
import { routes } from './app.routes';

// ... createTranslateLoader 함수 및 defaultLanguage 정의 (이전과 동일) ...

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    importProvidersFrom(
      TranslateModule.forRoot({
        defaultLanguage: defaultLanguage,
        loader: {
          provide: TranslateLoader,
          useFactory: createTranslateLoader,
          deps: [HttpClient],
        },
      }),
      // LocalizeRouterModule을 routes와 함께 설정
      LocalizeRouterModule.forRoot(routes, {
        parser: {
          provide: LocalizeParser,
          useFactory: (translate: TranslateService, location: Location, settings: LocalizeRouterSettings) =>
            new ManualParserLoader(translate, location, settings, ['en', 'ko']), // 지원하는 언어 목록
          deps: [TranslateService, Location, LocalizeRouterSettings],
        },
        initialNavigation: true,
      })
    ),
  ],
};

이제 scully를 실행하여 영문으로 변경한 뒤 새로고침을 해보면 static 파일이 영문 html이 생성되어 즉시 로딩 된 것을 확인할 수 있습니다. (국문도 동일합니다.)

2. 라우팅 경로에 skipRouteLocalization 설정

// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';

export const routes: Routes = [
  // localized를 적용합니다.
  { path: 'home', component: HomeComponent },
  
  // 적용하지 않습니다.
  { 
    path: 'login', 
    component: LoginComponent, 
    data: { skipRouteLocalization: true } 
  },
  
  // route에는 적용하지 않으나 redirect에만 적용합니다.
  { 
    path: 'logout', 
    redirectTo: 'login', 
    data: { skipRouteLocalization: { localizeRedirectTo: true } } 
  },

  // 기본 경로 설정
  { path: '', redirectTo: 'home', pathMatch: 'full' }
];

결론

@ gilsdav/ngx-translate-router는 localized 방식으로 동작합니다. 즉, 모든 언어의 라우터를 생성한 뒤 해당 라우터만 사용하는 방식입니다.

접근하는 시점에서 라우터를 가로 채고 localized된 route를 redirectTo 경로로 변환합니다. 예를 들어 /home/ko/home, /en/home 중 ngx-translate이 선택한 경로로 변환합니다.

라우터 변환을 일반 애플리케이션 변환과 분리하기 위해 접두사를 사용합니다. escapePrefix를 사용하여 접두사가 제거되고 세그먼트가 번역되지 않도록 할 수 있습니다.

이 라이브러리는 ngx-translate이 선택한 locales를 자동으로 인식하여 동작합니다.

this.translate.setDefaultLang();
this.translate.use();

참고 사이트

profile
Angular2+ Developer

0개의 댓글