rubyonrails.org에서 더 보기:

Ruby on Rails I18n(국제화) API

Ruby I18n(internationalization의 줄임말) gem은 Ruby on Rails와 함께 제공되며(Rails 2.2부터), 영어 외의 단일 커스텀 언어로 애플리케이션을 번역하거나 다국어 지원을 제공하기 위한 사용하기 쉽고 확장 가능한 프레임워크를 제공합니다.

"국제화" 프로세스는 일반적으로 모든 문자열과 로케일별 특정 부분(날짜나 통화 형식 등)을 애플리케이션에서 추상화하는 것을 의미합니다. "지역화" 프로세스는 이러한 부분에 대한 번역과 지역화된 형식을 제공하는 것을 의미합니다.1

따라서, Rails 애플리케이션을 국제화하는 과정에서 다음을 수행해야 합니다:

  • I18n 지원이 있는지 확인
  • Rails에게 로케일 사전의 위치를 알려주기
  • Rails에게 로케일을 설정, 유지 및 전환하는 방법을 알려주기

애플리케이션을 지역화하는 과정에서는 다음 세 가지를 수행하고 싶을 것입니다:

  • Rails의 기본 로케일 교체 또는 보완 - 예: 날짜와 시간 형식, 월 이름, Active Record 모델 이름 등
  • 애플리케이션의 문자열을 키가 있는 사전으로 추상화 - 예: flash 메시지, view의 정적 텍스트 등
  • 결과 사전을 어딘가에 저장

이 가이드는 I18n API를 설명하고 Rails 애플리케이션을 처음부터 국제화하는 방법에 대한 튜토리얼을 포함합니다.

이 가이드를 읽은 후에는 다음을 알 수 있습니다:

  • Ruby on Rails에서 I18n이 작동하는 방식
  • RESTful 애플리케이션에서 I18n을 다양한 방식으로 올바르게 사용하는 방법
  • Active Record 오류나 Action Mailer 이메일 제목을 번역하기 위해 I18n을 사용하는 방법
  • 애플리케이션의 번역 프로세스를 더 진행하기 위한 기타 도구들

Ruby I18n 프레임워크는 Rails 애플리케이션의 국제화/지역화에 필요한 모든 수단을 제공합니다. 추가 기능이나 특징을 더하기 위해 다양한 gem들을 사용할 수도 있습니다. 자세한 내용은 rails-i18n gem을 참조하세요.

1 Ruby on Rails에서 I18n이 작동하는 방식

국제화는 복잡한 문제입니다. 자연어는 너무 많은 방식으로 다릅니다(예: 복수형 규칙)그래서 모든 문제를 한 번에 해결하기 위한 도구를 제공하기가 어렵습니다. 이러한 이유로 Rails I18n API는 다음에 중점을 둡니다:

  • 영어와 유사한 언어에 대한 즉시 사용 가능한 지원 제공
  • 다른 언어를 위한 모든 것을 쉽게 커스터마이즈하고 확장할 수 있도록 함

이 해결책의 일환으로, Rails 프레임워크의 모든 정적 문자열 - 예: Active Record 유효성 검사 메시지, 시간과 날짜 형식 - 이 국제화되었습니다. Rails 애플리케이션의 지역화는 원하는 언어로 이러한 문자열에 대한 번역된 값을 정의하는 것을 의미합니다.

애플리케이션의 컨텐츠(예: 블로그 포스트)를 지역화, 저장 및 업데이트하려면 모델 컨텐츠 번역하기 섹션을 참조하세요.

1.1 라이브러리의 전체 아키텍처

따라서 Ruby I18n gem은 두 부분으로 나뉘어 있습니다:

  • I18n 프레임워크의 public API - 라이브러리의 작동 방식을 정의하는 public 메서드가 있는 Ruby 모듈
  • 이러한 메서드를 구현하는 기본 backend(의도적으로 Simple backend라고 명명됨)

사용자로서 여러분은 항상 I18n 모듈의 public 메서드만 접근해야 하지만, backend의 기능에 대해 아는 것이 유용합니다.

제공되는 Simple backend를 관계형 데이터베이스, GetText 사전 또는 유사한 것에 번역 데이터를 저장하는 더 강력한 것으로 교체할 수 있습니다. 아래의 Using different backends 섹션을 참조하세요.

1.2 Public I18n API

I18n API의 가장 중요한 메서드들은 다음과 같습니다:

translate # 텍스트 번역 검색
localize  # Date와 Time 객체를 로컬 형식으로 지역화

이것들은 #t와 #l이라는 별칭을 가지고 있어서 다음과 같이 사용할 수 있습니다:

I18n.t "store.title"
I18n.l Time.now

translate 메서드인 t() 와 localize 메서드인 l()의 축약형입니다.

다음과 같은 속성들에 대한 attribute reader와 writer도 있습니다:

load_path                 # 사용자 정의 번역 파일을 선언
locale                    # 현재 locale을 가져오고 설정 
default_locale            # 기본 locale을 가져오고 설정
available_locales         # 애플리케이션에서 사용 가능한 허용된 locale들
enforce_available_locales # locale 권한 강제화 (true 또는 false)
exception_handler         # 다른 exception_handler 사용
backend                   # 다른 backend 사용

다음 장에서 간단한 Rails 애플리케이션을 처음부터 국제화해봅시다!

2 Rails 애플리케이션의 국제화 설정하기

Rails 애플리케이션에서 I18n 지원을 시작하기 위한 몇 가지 단계가 있습니다.

2.1 I18n 모듈 설정하기

Rails는 'convention over configuration' 철학을 따라서 I18n이 적절한 기본 번역 문자열을 제공합니다. 다른 번역 문자열이 필요한 경우에는 이를 재정의할 수 있습니다.

Rails는 config/locales 디렉토리의 모든 .rb.yml 파일들을 자동으로 translations load path에 추가합니다.

이 디렉토리의 기본 en.yml locale은 다음과 같은 샘플 번역 문자열 쌍을 포함하고 있습니다:

en:
  hello: "안녕하세요 세상"

이것은 :en locale에서 hello key가 Hello world 문자열에 매핑된다는 것을 의미합니다. Rails 내부의 모든 문자열은 이러한 방식으로 국제화되어 있습니다. 예를 들어 activemodel/lib/active_model/locale/en.yml 파일의 Active Model validation 메시지나 activesupport/lib/active_support/locale/en.yml 파일의 시간과 날짜 형식을 참조하세요. 기본(Simple) backend에서 YAML이나 표준 Ruby Hash를 사용하여 번역을 저장할 수 있습니다.

I18n 라이브러리는 English기본 locale로 사용합니다. 즉, 다른 locale이 설정되지 않은 경우 번역을 찾을 때 :en이 사용됩니다.

i18n 라이브러리는 locale 키에 대해 일부 논의실용적인 접근 방식을 취합니다. "언어"와 "지역 설정" 또는 "방언"을 구분하는 데 전통적으로 사용되는 :"en-US" 또는 :"en-GB"와 같은 region 부분이 아닌, :en, :pl과 같은 locale("언어") 부분만 포함합니다. 많은 국제 애플리케이션은 :cs, :th, :es(체코어, 태국어, 스페인어)와 같이 locale의 "언어" 요소만 사용합니다. 하지만 서로 다른 언어 그룹 내에서도 중요할 수 있는 지역적 차이가 있습니다. 예를 들어, :"en-US" locale에서는 통화 기호로 $를 사용하지만, :"en-GB"에서는 £를 사용합니다. 이런 방식으로 지역 및 기타 설정을 구분하는 것을 막을 순 없습니다: :"en-GB" 사전에서 전체 "English - United Kingdom" locale을 제공하기만 하면 됩니다.

translations load path(I18n.load_path)는 자동으로 로드될 파일들의 경로 배열입니다. 이 경로를 구성하면 번역 디렉토리 구조와 파일 명명 체계를 사용자 정의할 수 있습니다.

backend는 번역이 처음 검색될 때 이러한 번역을 지연 로드합니다. 번역이 이미 선언된 후에도 이 backend를 다른 것으로 교체할 수 있습니다.

config/application.rb에서 다음과 같이 기본 locale과 번역 로드 경로를 구성할 수 있습니다:

config.i18n.load_path += Dir[Rails.root.join("my", "locales", "*.{rb,yml}")]
config.i18n.default_locale = :de

위는 다음을 설정합니다:

  • my/locales로부터 모든 로케일 파일(.rb와 .yml)을 불러오도록 로케일 로드 경로를 확장합니다.
  • 기본 로케일을 독일어(:de)로 설정합니다.

모든 translations을 찾기 전에 load path가 지정되어야 합니다. config/application.rb 대신 initializer에서 기본 locale을 변경하려면:

# config/initializers/locale.rb

# I18n 라이브러리가 translation 파일을 검색해야 하는 위치 
I18n.load_path += Dir[Rails.root.join("lib", "locale", "*.{rb,yml}")]

# 애플리케이션에서 사용 가능한 허용된 locale 목록
I18n.available_locales = [:en, :pt]

# 기본 locale을 :en이 아닌 다른 것으로 설정
I18n.default_locale = :pt

외부 gem의 번역을 재정의하려면 애플리케이션의 I18n 설정을 통해야 하며, I18n.load_path에 직접 추가하는 것은 작동하지 않습니다.

2.2 Request 간의 Locale 관리

지역화된 애플리케이션은 여러 개의 locale을 지원해야 할 것입니다. 이를 위해서는 각 request의 시작 시점에 locale을 설정하여 해당 request의 생명주기 동안 모든 문자열이 원하는 locale로 번역되도록 해야 합니다.

I18n.locale= 또는 I18n.with_locale을 사용하지 않는 한 기본 locale이 모든 번역에 사용됩니다.

모든 controller에서 일관되게 설정되지 않은 경우, I18n.locale은 동일한 thread/process에서 처리되는 후속 request에 영향을 미칠 수 있습니다. 예를 들어, POST request에서 I18n.locale = :es를 실행하면 locale을 설정하지 않는 모든 후속 controller request에 영향을 미치지만, 해당 특정 thread/process에만 영향을 미칩니다. 이러한 이유로, I18n.locale = 대신 이러한 누수 문제가 없는 I18n.with_locale을 사용할 수 있습니다.

ApplicationController에서 around_action을 사용하여 locale을 설정할 수 있습니다:

around_action :switch_locale

def switch_locale(&action)
  locale = params[:locale] || I18n.default_locale
  I18n.with_locale(locale, &action)
end

around_action을 사용하면 action을 호출하기 전과 후 어디에서나 locale을 전환할 수 있습니다. locale은 세션 내내 유지됩니다.

이 예제는 URL 쿼리 파라미터를 사용하여 locale을 설정하는 방법을 보여줍니다(예: http://example.com/books?locale=pt). 이 방식에서는 http://localhost:3000?locale=pt가 포르투갈어 현지화를 렌더링하고, http://localhost:3000?locale=de는 독일어 현지화를 로드합니다.

locale은 여러 가지 다른 방식을 사용하여 설정할 수 있습니다.

2.2.1 도메인 이름에서 Locale 설정하기

애플리케이션이 실행되는 도메인 이름에서 locale을 설정하는 옵션이 있습니다. 예를 들어, www.example.com은 영어(또는 기본) locale을 로드하고, www.example.es는 스페인어 locale을 로드하게 하고 싶을 수 있습니다. 이렇게 최상위 도메인 이름이 locale 설정에 사용됩니다. 이는 여러 가지 장점이 있습니다:

  • locale이 URL의 명확한 부분입니다.
  • 사람들이 직관적으로 콘텐츠가 어떤 언어로 표시될지 이해할 수 있습니다.
  • Rails에서 구현하기가 매우 간단합니다.
  • 검색 엔진은 서로 다른 언어의 콘텐츠가 서로 연결된 다른 도메인에 있는 것을 선호하는 것 같습니다.

ApplicationController에서 다음과 같이 구현할 수 있습니다:

around_action :switch_locale

def switch_locale(&action)
  locale = extract_locale_from_tld || I18n.default_locale
  I18n.with_locale(locale, &action)
end

# 최상위 도메인에서 locale을 가져오거나 해당 locale을 사용할 수 없는 경우 +nil+을 반환합니다
# 로컬에서 이것을 테스트하려면 /etc/hosts 파일에 다음과 같이 작성해야 합니다:
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
def extract_locale_from_tld
  parsed_locale = request.host.split(".").last
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

subdomain에서도 매우 비슷한 방식으로 locale을 설정할 수 있습니다:

# 요청 subdomain에서 locale 코드를 가져옵니다 (http://it.application.local:3000와 같은 형태)
# 이것을 로컬에서 테스트하려면 /etc/hosts 파일에 아래와 같이 작성해야 합니다:
#   127.0.0.1 it.application.local
#
# 추가로, config/environments/development.rb에 다음 설정을 추가해야 합니다:
#   config.hosts << 'it.application.local:3000'
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

애플리케이션이 locale 전환 메뉴를 포함하고 있다면, 다음과 같은 내용을 포함하게 됩니다:

link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")

APP_CONFIG[:deutsch_website_url]http://www.application.de와 같은 값으로 설정한다고 가정해봅시다.

이 솔루션은 앞서 언급한 장점들이 있지만, 서로 다른 도메인에서 다른 로케일("언어 버전")을 제공할 수 없거나 제공하고 싶지 않을 수 있습니다. 가장 명확한 해결책은 URL 파라미터(또는 요청 경로)에 locale 코드를 포함하는 것입니다.

2.2.2 URL 파라미터에서 Locale 설정하기

locale을 설정(및 전달)하는 가장 일반적인 방법은 첫 번째 예제의 I18n.with_locale(params[:locale], &action) around_action처럼 URL 파라미터에 포함시키는 것입니다. 이 경우 www.example.com/books?locale=ja 또는 www.example.com/ja/books와 같은 URL을 갖게 됩니다.

이 접근 방식은 도메인 이름에서 locale을 설정하는 것과 거의 동일한 장점들을 가집니다. 즉, RESTful하고 월드 와이드 웹의 나머지 부분과 조화를 이룬다는 점입니다. 다만 구현하는 데 약간 더 많은 작업이 필요합니다.

params에서 locale을 가져와서 그에 따라 설정하는 것은 어렵지 않습니다. 하지만 모든 URL에 포함시키고 따라서 요청을 통해 전달하는 것이 어렵습니다. 모든 URL에 link_to(books_url(locale: I18n.locale))와 같이 명시적 옵션을 포함시키는 것은 당연히 지루하고 아마도 불가능할 것입니다.

Rails는 ApplicationController#default_url_options에 "URL에 대한 동적 결정을 중앙화"하는 인프라를 포함하고 있는데, 이는 이러한 시나리오에서 유용합니다. url_for와 이에 의존하는 헬퍼 메서드들에 대한 "기본값"을 설정할 수 있게 해줍니다(default_url_options를 구현/오버라이딩함으로써).

그러면 ApplicationController에 다음과 같은 내용을 포함할 수 있습니다:

# app/controllers/application_controller.rb
def default_url_options
  { locale: I18n.locale }
end

default_url_options를 application controller에서 정의하면 Rails는 URL을 생성할 때마다 이 locale 정보를 포함시킵니다.

url_for에 의존하는 모든 helper 메서드(예: root_path 또는 root_url와 같은 named route helper, books_path 또는 books_url와 같은 resource route helper 등)는 이제 자동으로 locale을 query string에 포함시킵니다. 다음과 같은 형태입니다: http://localhost:3001/?locale=ja

이대로 만족할 수도 있습니다. 하지만 애플리케이션의 모든 URL 끝에 locale이 "붙어있으면" URL의 가독성에 영향을 미칩니다. 더욱이 아키텍처 관점에서 보면, locale은 일반적으로 애플리케이션 도메인의 다른 부분들보다 계층적으로 상위에 있으며 URL은 이를 반영해야 합니다.

아마도 URL이 다음과 같이 보이길 원할 것입니다: http://www.example.com/en/books(영어 locale을 로드)와 http://www.example.com/nl/books(네덜란드어 locale을 로드). 이는 위에서 언급한 "default_url_options를 오버라이딩하는" 전략으로 달성할 수 있습니다. scope를 사용하여 route를 설정하기만 하면 됩니다:

# config/routes.rb
scope "/:locale" do
  resources :books
end

이제 books_path 메서드를 호출하면 (기본 locale에서) "/en/books"를 반환받게 됩니다. http://localhost:3001/nl/books와 같은 URL은 네덜란드어 locale을 로드하고, 이어지는 books_path 호출은 "/nl/books"를 반환하게 됩니다 (locale이 변경되었기 때문에).

default_url_options의 반환값은 요청당 캐시되기 때문에, locale 선택기의 URL은 각 반복에서 해당하는 I18n.locale을 설정하는 루프 안에서 helper를 호출하여 생성할 수 없습니다. 대신, I18n.locale을 그대로 두고 helper에 명시적인 :locale 옵션을 전달하거나 request.original_fullpath를 수정하세요.

route에서 locale 사용을 강제하고 싶지 않다면 다음과 같이 선택적 path scope(괄호로 표시)를 사용할 수 있습니다:

# config/routes.rb
scope "(:locale)", locale: /en|nl/ do
  resources :books
end

(:locale)의 괄호는 이 부분이 선택적이라는 것을 의미합니다. 이 예에서는 routes가 /books에서도 매칭되고 /en/books에서도 매칭됩니다. scope에는 정규식으로 일치하는 locale 값만 받아들이도록 제약이 설정되어 있습니다.

이 접근 방식을 사용하면 locale 없이 http://localhost:3001/books와 같은 리소스에 접근할 때 Routing Error가 발생하지 않습니다. 이는 locale이 지정되지 않았을 때 기본 locale을 사용하고 싶은 경우에 유용합니다.

물론, 애플리케이션의 root URL(일반적으로 "homepage" 또는 "dashboard")에 대해서는 특별한 주의가 필요합니다. http://localhost:3001/nl과 같은 URL은 자동으로 작동하지 않습니다. routes.rbroot to: "dashboard#index" 선언이 locale을 고려하지 않기 때문입니다. (당연히 그렇습니다: "root" URL은 하나뿐이니까요.)

아마도 다음과 같은 URL들을 매핑해야 할 것입니다:

# config/routes.rb
get "/:locale" => "dashboard#index"

routes의 순서에 특별히 주의를 기울여서, 이 route 선언이 다른 것들을 "삼키지" 않도록 하세요. (root :to 선언 바로 앞에 추가하는 것이 좋습니다.)

route 작업을 단순화하는 다양한 gem들을 살펴보세요: routing_filter, route_translator.

2.2.3 사용자 환경설정에서 Locale 설정하기

인증된 사용자가 있는 애플리케이션의 경우, 사용자가 애플리케이션 인터페이스를 통해 locale 환경설정을 지정할 수 있습니다. 이 방식에서는 사용자가 선택한 locale 설정이 데이터베이스에 저장되고, 해당 사용자의 인증된 요청에 대해 locale을 설정하는 데 사용됩니다.

around_action :switch_locale

def switch_locale(&action)
  locale = current_user.try(:locale) || I18n.default_locale
  I18n.with_locale(locale, &action)
end

이 예시는 현재 사용자의 선호 locale이나 기본 locale을 기반으로 locale을 전환하는 around action을 보여줍니다. 여기서는 around_action이 각 action 호출 전후에 locale이 제대로 설정되었는지 확인합니다.

2.2.4 암묵적 Locale 선택하기

요청에 대해 명시적인 locale이 설정되지 않은 경우(예: 위의 방법들 중 하나를 통해), 애플리케이션은 원하는 locale을 추론하려고 시도해야 합니다.

2.2.4.1 Language Header에서 Locale 추론하기

Accept-Language HTTP 헤더는 요청에 대한 응답의 선호 언어를 나타냅니다. 브라우저는 사용자의 언어 환경설정을 기반으로 이 헤더 값을 설정하므로, locale을 추론할 때 좋은 첫 번째 선택이 됩니다.

Accept-Language 헤더를 사용하는 간단한 구현은 다음과 같습니다:

def switch_locale(&action)
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{locale}'" 
  I18n.with_locale(locale, &action)
end

private
  def extract_locale_from_accept_language_header
    request.env["HTTP_ACCEPT_LANGUAGE"].scan(/^[a-z]{2}/).first
  end

브라우저로부터 수신한 Accept-Language HTTP 헤더를 추출하고 요청된 locale을 로깅하는 방법을 보여주는 예입니다.

실제로는 이를 안정적으로 수행하기 위해서는 더 강력한 코드가 필요합니다. Iain Hecker의 http_accept_language 라이브러리나 Ryan Tomayko의 locale Rack middleware가 이 문제에 대한 해결책을 제공합니다.

2.2.4.2 IP 위치정보로부터 Locale 추론하기

요청을 보내는 클라이언트의 IP 주소를 사용하여 클라이언트의 지역과 locale을 추론할 수 있습니다. GeoLite2 Country와 같은 서비스나 geocoder와 같은 gem을 사용하여 이 방식을 구현할 수 있습니다.

일반적으로 이 방식은 language header를 사용하는 것보다 훨씬 덜 신뢰할 수 있으며 대부분의 웹 애플리케이션에서는 권장되지 않습니다.

2.2.5 Session이나 Cookies에 Locale 저장하기

경고: 선택한 locale을 session이나 cookie에 저장하고 싶을 수 있습니다. 하지만 그렇게 하지 마세요. locale은 투명해야 하며 URL의 일부여야 합니다. 이렇게 하면 웹 자체에 대한 사람들의 기본적인 가정을 깨트리지 않을 수 있습니다: URL을 친구에게 보내면 친구도 당신과 동일한 페이지와 콘텐츠를 볼 수 있어야 합니다. 이를 멋진 용어로 RESTful하다고 합니다. RESTful 접근방식에 대해 더 자세히 알아보려면 Stefan Tilkov의 글을 읽어보세요. 때때로 이 규칙에도 예외가 있으며 이는 아래에서 설명합니다.

3 Internationalization과 Localization

좋습니다! 이제 Ruby on Rails 애플리케이션에 대한 I18n 지원을 초기화하고 어떤 locale을 사용할지, 그리고 요청 간에 어떻게 유지할지 설정했습니다.

다음으로는 모든 locale별 요소를 추상화하여 애플리케이션을 internationalize 해야 합니다. 마지막으로, 이러한 추상화에 필요한 번역을 제공하여 localize 해야 합니다.

다음 예시를 보겠습니다:

# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action) 
  end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = "Hello Flash"
  end
end
<!-- app/views/home/index.html.erb -->
<h1>안녕하세요</h1> 
<p><%= flash[:notice] %></p>

rails i18n demo untranslated

3.1 지역화된 코드 추상화하기

우리의 코드에는 응답으로 렌더링될 두 개의 영어 문자열("Hello Flash"와 "Hello World")이 있습니다. 이 코드를 국제화하기 위해서는 각 문자열을 Rails의 #t helper 호출로 대체해야 하며 각각 적절한 key를 사용해야 합니다:

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = t(:hello_flash)
  end
end
<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1> 
<p><%= flash[:notice] %></p>

이제 이 view가 렌더링되면 :hello_world:hello_flash key에 대한 translation이 없다고 알려주는 에러 메시지가 표시됩니다.

rails i18n demo translation missing

Rails는 view에 t (translate) 헬퍼 메서드를 추가하여 매번 I18n.t를 입력할 필요가 없도록 합니다. 추가로 이 헬퍼는 누락된 translation을 잡아내어 발생한 에러 메시지를 <span class="translation_missing">으로 감싸줍니다.

3.2 국제화된 문자열에 대한 번역 제공하기

translation dictionary 파일에 누락된 translation을 추가하세요:

# config/locales/en.yml
en:
  hello_world: 안녕하세요 세상!
  hello_flash: 안녕하세요 flash!
# config/locales/pirate.yml
pirate:
  hello_world: 안녕하세요 세계
  hello_flash: 안녕하세요 Flash

default_locale이 변경되지 않았기 때문에, 번역은 :en locale을 사용하고 응답은 영어 문자열을 렌더링합니다:

rails i18n demo translated to English

locale이 URL을 통해 pirate locale로 설정된 경우(http://localhost:3000?locale=pirate), 응답은 pirate 문자열을 렌더링합니다:

rails i18n demo translated to pirate

새로운 locale 파일을 추가할 때는 서버를 재시작해야 합니다.

SimpleStore에서 번역을 저장하기 위해 YAML(.yml) 또는 일반 Ruby(.rb) 파일을 사용할 수 있습니다. YAML은 Rails 개발자들 사이에서 선호되는 옵션입니다. 하지만 큰 단점이 하나 있습니다. YAML은 공백과 특수 문자에 매우 민감하므로, 애플리케이션이 사전을 제대로 로드하지 못할 수 있습니다. Ruby 파일은 첫 번째 요청에서 애플리케이션이 중단되므로 무엇이 잘못되었는지 쉽게 찾을 수 있습니다. (YAML 사전에서 "이상한 문제"가 발생하면, 관련 부분을 Ruby 파일로 옮겨보세요.)

YAML 파일에 번역을 저장하는 경우, 다음 키들은 이스케이프 처리되어야 합니다:

  • true, on, yes
  • false, off, no

예시:

# config/locales/en.yml
en:
  success:
    'true':  '참!'
    'on':    '켜짐!'
    'false': '거짓!'
  failure:
    true:    '참!'
    off:     '꺼짐!'
    false:   '거짓!'
I18n.t "success.true"  # => 'True!'  
I18n.t "success.on"    # => 'On!'
I18n.t "success.false" # => 'False!'
I18n.t "failure.false" # => 번역 누락
I18n.t "failure.off"   # => 번역 누락 
I18n.t "failure.true"  # => 번역 누락

3.3 번역에 변수 전달하기

어플리케이션을 성공적으로 국제화하기 위한 핵심 고려사항 중 하나는 지역화된 코드를 추상화할 때 문법 규칙에 대한 잘못된 가정을 하지 않는 것입니다. 한 로케일에서 기본적인 것처럼 보이는 문법 규칙이 다른 로케일에서는 적용되지 않을 수 있습니다.

다음 예시는 부적절한 추상화를 보여주며, 번역의 다른 부분들의 순서에 대해 가정을 하고 있습니다. Rails가 이러한 경우를 처리하기 위해 number_to_currency helper를 제공한다는 점을 참고하세요.

<!-- app/views/products/show.html.erb -->
<%= "#{t('currency')}#{@product.price}" %>
# config/locales/en.yml
en:
  currency: "$"
# config/locales/es.yml
es:
  currency: "€"

상품의 가격이 10이라면 적절한 스페인어 번역은 "€10"이 아닌 "10 €"이지만 추상화로는 이를 제공할 수 없습니다.

적절한 추상화를 만들기 위해, I18n gem은 변수 보간(interpolation)이라는 기능을 제공합니다. 이를 통해 번역 정의에 변수를 사용하고 이러한 변수들의 값을 번역 메서드에 전달할 수 있습니다.

적절한 추상화의 예는 다음과 같습니다:

<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
# config/locales/en.yml
en:
  product_price: "$%{price}"
# config/locales/es.yml
es:
  product_price: "%{price} €" 

모든 문법과 구두점에 대한 결정은 정의 자체에서 이루어지므로, 추상화를 통해 적절한 번역을 제공할 수 있습니다.

defaultscope 키워드는 예약어이므로 변수명으로 사용할 수 없습니다. 만약 사용하면 I18n::ReservedInterpolationKey 예외가 발생합니다. 번역에 interpolation 변수가 필요하지만 이것이 #translate에 전달되지 않은 경우 I18n::MissingInterpolationArgument 예외가 발생합니다.

3.4 Date/Time 포맷 추가하기

OK! 이제 date/time 현지화 기능도 시연할 수 있도록 view에 timestamp를 추가해봅시다. 시간 포맷을 현지화하려면 Time 객체를 I18n.l에 전달하거나 (가급적) Rails의 #l 헬퍼를 사용하면 됩니다. :format 옵션을 전달하여 포맷을 선택할 수 있으며 - 기본적으로는 :default 포맷이 사용됩니다.

<!-- app/views/home/index.html.erb -->
<h1><%= t :hello_world %></h1>
<p><%= flash[:notice] %></p>
<p><%= l Time.now, format: :short %></p>

그리고 우리의 pirate 번역 파일에 시간 형식을 추가해봅시다 (이것은 이미 Rails의 영어 기본값에 있습니다):

# config/locales/pirate.yml
pirate:
  time:
    formats:
      short: "arrrround %H'ish"

그러면 다음과 같은 결과를 얻을 수 있습니다:

rails i18n demo localized time to pirate

현재 I18n 백엔드가 예상대로 작동하도록 하려면 (적어도 'pirate' 로케일의 경우) 더 많은 날짜/시간 포맷을 추가해야 할 수 있습니다. 물론, 누군가가 이미 당신의 로케일에 대한 Rails 기본값을 번역해놓았을 가능성이 큽니다. 다양한 로케일 파일의 아카이브를 보려면 GitHub의 rails-i18n 저장소를 참조하세요. 이러한 파일을 config/locales/ 디렉토리에 넣으면 자동으로 사용할 준비가 됩니다.

3.5 다른 로케일을 위한 Inflection 규칙

Rails는 영어 외의 로케일에 대한 inflection 규칙(단수화 및 복수화 규칙과 같은)을 정의할 수 있도록 해줍니다. config/initializers/inflections.rb에서 여러 로케일에 대한 이러한 규칙을 정의할 수 있습니다. 이 initializer는 영어에 대한 추가 규칙을 지정하는 기본 예시를 포함하고 있습니다. 필요에 따라 다른 로케일에 대해서도 동일한 형식을 따르시면 됩니다.

3.6 지역화된 View들

애플리케이션에 BooksController가 있다고 해봅시다. index 액션은 app/views/books/index.html.erb 템플릿의 내용을 렌더링합니다. 이 템플릿의 지역화된 변형index.es.html.erb를 같은 디렉토리에 넣으면, locale이 :es로 설정되었을 때 Rails는 이 템플릿의 내용을 렌더링할 것입니다. locale이 기본값으로 설정되어 있을 때는 일반적인 index.html.erb view가 사용됩니다. (향후 Rails 버전에서는 이러한 자동화된 지역화가 public 등의 asset에도 적용될 수 있습니다.)

이 기능은 예를 들어 YAML이나 Ruby dictionary에 넣기에는 너무 많은 정적 콘텐츠를 다룰 때 활용할 수 있습니다. 하지만 나중에 템플릿을 수정하고 싶을 때는 모든 템플릿에 변경사항을 반영해야 한다는 점을 유의하세요.

3.7 Locale 파일의 구성

i18n 라이브러리와 함께 제공되는 기본 SimpleStore를 사용할 때, dictionary들은 디스크의 일반 텍스트 파일에 저장됩니다. 애플리케이션의 모든 부분에 대한 번역을 locale당 하나의 파일에 넣는 것은 관리하기 어려울 수 있습니다. 이러한 파일들을 사용자가 이해하기 쉬운 계층 구조로 저장할 수 있습니다.

예를 들어, config/locales 디렉토리는 다음과 같이 구성될 수 있습니다:

|-defaults
|---es.yml
|---en.yml
|-models  
|---book
|-----es.yml
|-----en.yml
|-views
|---defaults
|-----es.yml  
|-----en.yml
|---books
|-----es.yml
|-----en.yml  
|---users
|-----es.yml
|-----en.yml
|---navigation  
|-----es.yml
|-----en.yml

이렇게 하면 model과 model attribute names를 view 내의 텍스트와 분리할 수 있고, 이 모든 것을 "defaults"(예: date와 time 포맷)와도 분리할 수 있습니다. i18n 라이브러리의 다른 저장소들은 이러한 분리를 위한 다른 수단을 제공할 수 있습니다.

Rails의 기본 locale 로딩 메커니즘은 여기서처럼 중첩된 dictionary에 있는 locale 파일들을 로드하지 않습니다. 따라서 이것이 작동하려면, Rails에게 더 깊이 찾아보도록 명시적으로 지시해야 합니다:

# config/application.rb
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]

이는 config/locales 및 하위 디렉토리에 있는 모든 커스텀 locale 파일을 로드합니다.

4 Overview of the I18n API Features

이제 i18n 라이브러리 사용법을 잘 이해하고 기본적인 Rails 애플리케이션을 국제화하는 방법을 알게 되었을 것입니다. 다음 장에서는 이러한 기능들을 더 자세히 다룰 것입니다.

이 장들에서는 I18n.translate 메서드와 translate view helper method 두 가지를 모두 사용하는 예제를 보여줄 것입니다 (view helper 메서드가 제공하는 추가 기능들을 주목하세요).

다음과 같은 기능들을 다룹니다:

  • 번역문 찾기
  • 번역문에 데이터 삽입하기
  • 번역문 복수화하기
  • 안전한 HTML 번역 사용하기 (view helper 메서드에서만)
  • 날짜, 숫자, 통화 등의 지역화

4.1 번역 찾아보기

4.1.1 기본 검색, Scope 및 중첩 키

번역은 Symbol이나 String이 될 수 있는 키를 통해 찾을 수 있으므로, 다음 호출들은 동일합니다:

I18n.t :message  
I18n.t "message"

둘 다 같은 결과를 리턴합니다.

translate 메서드는 번역 키의 "네임스페이스" 또는 scope를 지정하는 데 사용할 수 있는 하나 이상의 추가 키를 포함할 수 있는 :scope 옵션도 사용할 수 있습니다:

I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

이는 Active Record 에러 메시지에서 :record_invalid 메시지를 조회합니다.

추가로, key와 scopes 모두 다음과 같이 점으로 구분된 키로 지정할 수 있습니다:

I18n.translate "activerecord.errors.messages.record_invalid"

따라서 다음의 호출은 동일합니다:

I18n.t "activerecord.errors.messages.record_invalid" 
I18n.t "errors.messages.record_invalid", scope: :activerecord
I18n.t :record_invalid, scope: "activerecord.errors.messages"
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

위의 모든 구문들은 동일한 결과를 출력합니다.

4.1.2 기본값

:default 옵션이 주어지면, translation이 없는 경우 해당 값이 반환됩니다:

I18n.t :missing, default: "Not here" 
# => 'Not here'

:default 값이 Symbol인 경우, 이것은 key로 사용되어 번역됩니다. default로 여러 값을 제공할 수 있습니다. 값이 반환되는 첫 번째 것이 사용됩니다.

예를 들어, 다음은 먼저 :missing key를 번역하고 그 다음 :also_missing key를 번역하려고 시도합니다. 두 key 모두 결과를 생성하지 않으므로 "Not here" 문자열이 반환됩니다:

I18n.t :missing, default: [:also_missing, "여기 없음"]
# => '여기 없음'

4.1.3 Bulk 및 Namespace 조회

한번에 여러 번역을 조회하기 위해서는 키의 배열을 전달할 수 있습니다:

I18n.t [:odd, :even], scope: "errors.messages"
# => ["홀수여야 합니다", "짝수여야 합니다"] 

또한 key는 그룹화된 번역의 (잠재적으로 중첩된) hash로 번역될 수 있습니다. 예를 들어, Active Record error 메시지를 Hash로 모두 받아볼 수 있습니다:

I18n.t "errors.messages"
# => {:inclusion=>"목록에 포함되어 있지 않습니다", :exclusion=> ... }

번역 해시를 일괄적으로 interpolation하려면 파라미터로 deep_interpolation: true를 전달해야 합니다. 다음과 같은 딕셔너리가 있을 때:

ko:
  welcome:
    title: "환영합니다!"
    content: "%{app_name}에 오신 것을 환영합니다"

그러면 이 설정이 없을 경우 중첩된 interpolation은 무시됩니다:

I18n.t "welcome", app_name: "book store"
# => {:title=>"환영합니다!", :content=>"%{app_name}에 오신 것을 환영합니다"}

I18n.t "welcome", deep_interpolation: true, app_name: "book store"  
# => {:title=>"환영합니다!", :content=>"book store에 오신 것을 환영합니다"}

4.1.4 "Lazy" Lookup

Rails는 views 내부에서 locale을 조회하는 편리한 방법을 제공합니다. 다음과 같은 dictionary가 있을 때:

ko:
  books:
    index:
      title: "제목"

books.index.title 값을 app/views/books/index.html.erb 템플릿 내부에서 다음과 같이 조회할 수 있습니다 (점을 주의하세요):

<%= t '.title' %>

자동 부분 번역 범위 지정은 translate 뷰 헬퍼 메소드에서만 사용할 수 있습니다.

"Lazy" 검색은 컨트롤러에서도 사용할 수 있습니다:

ko:
  books:
    create:
      success: 책이 생성되었습니다!

이것은 예를 들어 flash 메시지를 설정할 때 유용합니다:

class BooksController < ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t(".success") 
  end
end

4.2 Pluralization

영어를 포함한 많은 언어들에는 주어진 문자열에 대해 단수형과 복수형 두 가지 형태만 있습니다. 예를 들어 "1 message"와 "2 messages"가 있습니다. 다른 언어들(Arabic, Japanese, Russian 등 더 많은 언어들)은 추가적이거나 더 적은 복수형을 가진 다른 문법 구조를 가지고 있습니다. 따라서 I18n API는 유연한 복수화 기능을 제공합니다.

:count interpolation 변수는 번역에 interpolate되면서 동시에 복수화 backend에 정의된 복수화 규칙에 따라 번역문에서 복수형을 선택하는데 사용되는 특별한 역할을 합니다. 기본적으로는 영어 복수화 규칙만 적용됩니다.

I18n.backend.store_translations :en, inbox: {
  zero: "메시지 없음", # 선택사항
  one: "메시지 1개",
  other: "메시지 %{count}개"
}
I18n.translate :inbox, count: 2
# => '메시지 2개'

I18n.translate :inbox, count: 1
# => '메시지 1개'

I18n.translate :inbox, count: 0
# => '메시지 없음'

:en의 복수형 알고리즘은 다음과 같이 간단합니다:

lookup_key = count가 0이고 entry가 :zero key를 가지고 있다면 :zero로 설정
lookup_key ||= count가 1이면 :one, 아니면 :other
entry[lookup_key]

번역어 :one은 단수형으로 간주되고, :other는 복수형으로 사용됩니다. 수가 0이고 :zero 항목이 있는 경우, :other 대신 해당 항목이 사용됩니다.

키에 대한 조회 결과가 복수화에 적합한 Hash가 아닌 경우, I18n::InvalidPluralizationData 예외가 발생합니다.

4.2.1 로케일별 규칙

I18n gem은 로케일별 규칙을 활성화하는데 사용할 수 있는 Pluralization 백엔드를 제공합니다. Simple 백엔드에 이를 포함시킨 다음, i18n.plural.rule로 번역 저장소에 로케일별 복수화 알고리즘을 추가하세요.

I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }
I18n.backend.store_translations :pt, apples: { one: "하나 또는 없음", other: "하나보다 많음" }

I18n.t :apples, count: 0, locale: :pt  
# => '하나 또는 없음' 

선택적으로, 별도의 gem인 rails-i18n을 사용하여 locale별 복수화 규칙의 더 완전한 세트를 제공할 수 있습니다.

4.3 로케일 설정과 전달

로케일은 I18n.locale에 유사 전역적으로 설정할 수 있고(Time.zone과 같은 방식으로 Thread.current를 사용), #translate#localize에 옵션으로 전달할 수 있습니다.

로케일이 전달되지 않으면 I18n.locale이 사용됩니다:

I18n.locale = :de
I18n.t :foo
I18n.l Time.now

Rails는 localization 매소드 l()과 translation 매소드 t()의 shorthand 형태를 제공하므로, 아래처럼 작성할 수 있습니다:

명시적으로 locale 전달하기:

I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de

이렇게 하면 I18n.locale의 기본값을 변경하지 않고도 임의의 locale을 사용할 수 있습니다.

I18n.localeI18n.default_locale을 기본값으로 가지며 이는 :en으로 기본 설정됩니다. 기본 로케일은 다음과 같이 설정할 수 있습니다:

I18n.default_locale = :de

4.4 안전한 HTML 번역 사용하기

'_html' 접미사가 있는 key들과 'html'이라는 이름의 key들은 HTML safe로 표시됩니다. view에서 이들을 사용할 때 HTML은 escape되지 않습니다.

# config/locales/en.yml
en:
  welcome: <b>환영합니다!</b>
  hello_html: <b>안녕하세요!</b>
  title:
    html: <b>제목!</b>
<!-- app/views/home/index.html.erb -->
<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>

필요한 경우 interpolation은 escape됩니다. 예를 들어 다음의 경우:

en:
  welcome_html: "<b>환영합니다 %{username}!</b>"

사용자가 설정한 username을 안전하게 전달할 수 있습니다:

<%# 이것은 안전합니다. 필요한 경우 이스케이프 처리됩니다. %>
<%= t('welcome_html', username: @current_user.username) %>

반면에 safe string은 있는 그대로 삽입됩니다.

HTML safe translate 텍스트로의 자동 변환은 translate (또는 t) helper method에서만 사용할 수 있습니다. 이는 view와 controller에서 작동합니다.

i18n demo HTML safe

4.5 Active Record Model에 대한 번역

Model.model_name.humanModel.human_attribute_name(attribute) 메소드를 사용하여 model과 attribute의 이름에 대한 번역을 명확하게 조회할 수 있습니다.

예를 들어 다음과 같은 번역을 추가하면:

ko:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle" 
      # User 속성 "login"이 "Handle"로 번역됩니다

그러면 User.model_name.human은 "Customer"를 반환하고 User.human_attribute_name("login")은 "Handle"을 반환할 것입니다.

다음과 같이 model 이름의 복수형도 설정할 수 있습니다:

ko:
  activerecord:
    models:
      user:
        one: Customer
        other: Customers

그러면 User.model_name.human(count: 2)는 "Customers"를 반환할 것입니다. count: 1이나 파라미터 없이 사용하면 "Customer"를 반환합니다.

주어진 모델 내에서 중첩된 속성에 접근해야 하는 경우, 번역 파일의 모델 레벨에서 이러한 속성들을 model/attribute 아래에 중첩해야 합니다:

en:
  activerecord:
    attributes:
      user/role:
        admin: "관리자"
        contributor: "기여자"

그러면 User.human_attribute_name("role.admin")은 "Admin"을 반환할 것입니다.

ActiveRecord::Base를 상속받지 않고 ActiveModel을 포함하는 클래스를 사용하는 경우, 위의 키 경로에서 activerecordactivemodel로 교체하세요.

4.5.1 에러 메시지 스코프

Active Record 유효성 검사 에러 메시지도 쉽게 번역할 수 있습니다. Active Record는 특정 모델, 속성 및/또는 유효성 검사에 대해 서로 다른 메시지와 번역을 제공할 수 있도록 메시지 번역을 배치할 수 있는 몇 가지 네임스페이스를 제공합니다. 또한 단일 테이블 상속을 투명하게 처리합니다.

이를 통해 애플리케이션의 요구사항에 맞게 메시지를 유연하게 조정할 수 있는 강력한 수단을 제공합니다.

다음과 같이 name 속성에 대한 유효성 검사가 있는 User 모델을 고려해보세요:

class User < ApplicationRecord
  validates :name, presence: true
end

이 경우 에러 메시지의 key는 :blank입니다. 따라서, 우리의 예시에서는 다음 key들을 이 순서대로 시도하고 첫 번째 결과를 반환합니다.

activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank  
errors.messages.blank

더 추상적으로 설명하면, 다음 목록의 순서대로 매칭되는 첫 번째 key를 반환합니다.

activerecord.errors.models.[model_name].attributes.[attribute_name].[key]
activerecord.errors.models.[model_name].[key]
activerecord.errors.messages.[key]
errors.attributes.[attribute_name].[key]
errors.messages.[key]

모델이 상속을 추가적으로 사용하는 경우, message는 상속 체인을 따라 조회됩니다.

예를 들어, User를 상속하는 Admin 모델이 있을 수 있습니다:

class Admin < User
  validates :name, presence: true
end

그러면 Active Record는 다음 순서로 메시지를 찾습니다:

activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank  
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

이렇게 하면 모델의 상속 체인과 속성, 모델 또는 기본 scope에서 서로 다른 지점의 다양한 에러 메시지에 대해 특별한 번역을 제공할 수 있습니다.

4.5.2 에러 메시지 보간

번역된 모델 이름, 번역된 속성 이름, 그리고 값은 항상 각각 model, attribute, value로 보간이 가능합니다.

예를 들어, 기본 에러 메시지인 "cannot be blank" 대신에 다음과 같이 속성 이름을 사용할 수 있습니다: "Please fill in your %{attribute}".

  • 사용 가능한 경우 count는 복수형을 위해 사용될 수 있습니다:
validation with option message interpolation
confirmation - :confirmation attribute
acceptance - :accepted -
presence - :blank -
absence - :present -
length :within, :in :too_short count
length :within, :in :too_long count
length :is :wrong_length count
length :minimum :too_short count
length :maximum :too_long count
uniqueness - :taken -
format - :invalid -
inclusion - :inclusion -
exclusion - :exclusion -
associated - :invalid -
non-optional association - :required -
numericality - :not_a_number -
numericality :greater_than :greater_than count
numericality :greater_than_or_equal_to :greater_than_or_equal_to count
numericality :equal_to :equal_to count
numericality :less_than :less_than count
numericality :less_than_or_equal_to :less_than_or_equal_to count
numericality :other_than :other_than count
numericality :only_integer :not_an_integer -
numericality :in :in count
numericality :odd :odd -
numericality :even :even -
comparison :greater_than :greater_than count
comparison :greater_than_or_equal_to :greater_than_or_equal_to count
comparison :equal_to :equal_to count
comparison :less_than :less_than count
comparison :less_than_or_equal_to :less_than_or_equal_to count
comparison :other_than :other_than count

4.6 Action Mailer E-Mail 제목을 위한 번역

만약 mail 메소드에 subject를 전달하지 않으면, Action Mailer는 번역에서 이를 찾으려고 시도합니다. 검색은 키를 생성하기 위해 <mailer_scope>.<action_name>.subject 패턴을 사용하여 수행됩니다.

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    #...
  end
end
ko:
  user_mailer:
    welcome:
      subject: "Rails Guides에 오신 것을 환영합니다!"

mailer에서 interpolation에 파라미터를 전달하기 위해서는 default_i18n_subject 메서드를 사용하세요.

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end
ko:
  user_mailer:
    welcome:
      subject: "%{user}, Rails Guides에 오신 것을 환영합니다!"

4.7 I18n 지원을 제공하는 다른 내장 메서드들의 개요

Rails는 고정된 문자열과 format 문자열 및 기타 format 정보와 같은 지역화를 몇 가지 helper에서 사용합니다. 다음은 간단한 개요입니다.

4.7.1 Action View Helper 메서드

  • distance_of_time_in_words는 결과를 번역하고 복수형으로 변환하며 초, 분, 시간 등의 숫자를 보간합니다. datetime.distance_in_words 번역을 참조하세요.

  • datetime_selectselect_month는 select 태그를 채울 때 번역된 월 이름을 사용합니다. 번역은 date.month_names를 참조하세요. datetime_select는 또한 order 옵션을 명시적으로 전달하지 않는 한 date.order에서 order 옵션을 찾습니다. 모든 날짜 선택 helper는 해당되는 경우 datetime.prompts 범위의 번역을 사용하여 prompt를 번역합니다.

  • number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter, 그리고 number_to_human_size helper는 number 범위에 있는 숫자 format 설정을 사용합니다.

4.7.2 Active Model 메서드

  • model_name.humanhuman_attribute_nameactiverecord.models 범위에서 사용 가능한 경우 모델 이름과 속성 이름에 대한 번역을 사용합니다. 또한 위의 "Error message scopes"에서 설명한 대로 상속된 클래스 이름에 대한 번역도 지원합니다(예: STI와 함께 사용).

  • ActiveModel::Errors#generate_message(Active Model validation에서 사용되지만 수동으로도 사용 가능)는 model_name.humanhuman_attribute_name을 사용합니다(위 참조). 또한 에러 메시지를 번역하고 "Error message scopes"에서 설명한 대로 상속된 클래스 이름에 대한 번역을 지원합니다.

  • ActiveModel::Error#full_messageActiveModel::Errors#full_messageserrors.format에서 찾은 format을 사용하여 속성 이름을 에러 메시지 앞에 추가합니다(기본값: "%{attribute} %{message}"). 기본 format을 커스터마이징하려면 앱의 locale 파일에서 이를 재정의하세요. 모델별 또는 속성별로 format을 커스터마이징하려면 [config.active_model.i18n_customize_full_message][]를 참조하세요.

4.7.3 Active Support 메서드

  • Array#to_sentencesupport.array 범위에 지정된 format 설정을 사용합니다.

5 사용자 정의 번역을 저장하는 방법

Active Support와 함께 제공되는 Simple backend는 Ruby와 YAML 형식 모두로 번역을 저장할 수 있게 해줍니다.2

예를 들어 번역을 제공하는 Ruby Hash는 다음과 같을 수 있습니다:

{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

동등한 YAML 파일은 다음과 같이 보일 것입니다:

pt:
  foo:
    bar: baz

보시다시피, 두 경우 모두 최상위 키는 locale입니다. :foo는 namespace 키이고 :bar는 "baz" 번역을 위한 키입니다.

다음은 Active Support en.yml 번역 YAML 파일의 "실제" 예시입니다:

ko:
  date:
    formats: 
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"

따라서 다음과 같이 동일한 모든 lookup은 :short date format "%b %d"를 반환합니다:

I18n.t "date.formats.short"  
I18n.t "formats.short", scope: :date
I18n.t :short, scope: "date.formats"
I18n.t :short, scope: [:date, :formats]

일반적으로 번역을 저장하는 형식으로 YAML을 사용하는 것을 권장합니다. 하지만 특별한 날짜 형식과 같이 locale 데이터의 일부로 Ruby lambda를 저장하고 싶은 경우도 있습니다.

6 I18n Setup 커스터마이징

6.1 다른 Backend 사용하기

여러 가지 이유로 Active Support에 포함된 Simple backend는 Ruby on Rails[3]를 위해 "가능한 한 가장 단순한 것"만을 구현합니다... 즉, 영어와 영어와 매우 유사한 언어에 대해서만 작동이 보장됩니다. 또한, Simple backend는 번역을 읽을 수만 있고 어떤 형식으로도 동적으로 저장할 수 없습니다.

하지만 이러한 제약에 갇혀있을 필요는 없습니다. Ruby I18n gem은 Simple backend 구현을 I18n.backend= setter에 backend 인스턴스를 전달하여 여러분의 필요에 더 잘 맞는 다른 것으로 쉽게 교체할 수 있게 해줍니다.

예를 들어, Simple backend를 Chain backend로 교체하여 여러 backend를 함께 연결할 수 있습니다. 이는 Simple backend로 표준 번역을 사용하면서 커스텀 애플리케이션 번역을 데이터베이스나 다른 backend에 저장하고 싶을 때 유용합니다.

Chain backend를 사용하면 Active Record backend를 사용하고 (기본) Simple backend로 폴백할 수 있습니다:

I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)

위와 같이 설정하면 먼저 ActiveRecord backend를 사용해 번역을 찾고, 찾지 못한 경우 Simple backend로 폴백합니다.

6.2 다른 Exception Handler 사용하기

I18n API는 예상치 못한 상황이 발생했을 때 backend에서 발생시키는 다음과 같은 exception들을 정의합니다:

Exception 이유
I18n::MissingTranslationData 요청된 key에 대한 번역을 찾을 수 없음
I18n::InvalidLocale I18n.locale에 설정된 locale이 유효하지 않음 (예: nil)
I18n::InvalidPluralizationData count 옵션이 전달되었으나 번역 데이터가 복수화에 적합하지 않음
I18n::MissingInterpolationArgument 번역에 필요한 interpolation 인자가 전달되지 않음
I18n::ReservedInterpolationKey 번역에 예약된 interpolation 변수명이 포함됨 (scope, default 중 하나)
I18n::UnknownFileType backend가 I18n.load_path에 추가된 파일 타입을 처리할 수 없음

6.2.1 I18n::MissingTranslationData 처리 방식 커스터마이징

config.i18n.raise_on_missing_translationstrue이면, view와 controller에서 I18n::MissingTranslationData 에러가 발생합니다. 값이 :strict이면 model에서도 에러가 발생합니다. 누락된 번역이 요청되는 곳을 찾을 수 있도록 테스트 환경에서 이 옵션을 활성화하는 것이 좋습니다.

config.i18n.raise_on_missing_translationsfalse이면 (모든 환경에서의 기본값), exception의 에러 메시지가 출력됩니다. 이 메시지에는 누락된 key/scope가 포함되어 있어 코드를 수정할 수 있습니다.

이 동작을 더 커스터마이징하고 싶다면, config.i18n.raise_on_missing_translations = false로 설정하고 I18n.exception_handler를 구현해야 합니다. 커스텀 exception handler는 proc이나 call 메서드를 가진 클래스가 될 수 있습니다:

# config/initializers/i18n.rb 
module I18n
  class RaiseExceptForSpecificKeyExceptionHandler
    def call(exception, locale, key, options)
      if key == "special.key"
        "translation missing!" # 이것을 반환하고 raise하지 않음
      elsif exception.is_a?(MissingTranslation) 
        raise exception.to_exception
      else
        raise exception
      end
    end
  end
end

I18n.exception_handler = I18n::RaiseExceptForSpecificKeyExceptionHandler.new

이는 I18n.t("special.key")의 경우를 제외하고 기본 핸들러와 동일한 방식으로 모든 예외를 발생시킬 것입니다.

7 모델 컨텐츠 번역하기

이 가이드에서 설명한 I18n API는 주로 인터페이스 문자열을 번역하기 위한 것입니다. 모델 컨텐츠(예: 블로그 포스트)를 번역하려면 이를 도와줄 다른 솔루션이 필요합니다.

다음과 같은 여러 gem이 도움이 될 수 있습니다:

  • Mobility: 번역 테이블, JSON 컬럼(PostgreSQL) 등 다양한 형식으로 번역을 저장하는 기능을 제공합니다.
  • Traco: 번역 가능한 컬럼을 모델 테이블 자체에 저장합니다.

8 결론

이제 Ruby on Rails의 I18n 지원이 어떻게 작동하는지에 대한 좋은 개요를 가지게 되었고 프로젝트 번역을 시작할 준비가 되었을 것입니다.

9 Rails I18n에 기여하기

Ruby on Rails의 I18n 지원은 2.2 릴리스에서 도입되었으며 계속 발전하고 있습니다. 이 프로젝트는 먼저 gem과 실제 애플리케이션에서 솔루션을 발전시키고, 그 다음 가장 널리 유용한 기능 중 최고의 기능을 선별하여 코어에 포함시키는 좋은 Ruby on Rails 개발 전통을 따릅니다.

따라서 우리는 모든 사람이 gem이나 다른 라이브러리에서 새로운 아이디어와 기능을 실험하고 커뮤니티에 공개하도록 권장합니다. (메일링 리스트에 여러분의 작업을 알리는 것을 잊지 마세요!)

Ruby on Rails용 예제 번역 데이터 저장소에서 자신의 로케일(언어)이 누락된 것을 발견하면, 저장소를 fork하고, 데이터를 추가한 다음 pull request를 보내주세요.

10 리소스

  • GitHub: rails-i18n - rails-i18n 프로젝트의 코드 저장소와 이슈 트래커입니다. 가장 중요한 것은 대부분의 경우 애플리케이션에서 작동할 수 있는 Rails용 예제 번역을 많이 찾을 수 있다는 점입니다.
  • GitHub: i18n - i18n gem의 코드 저장소와 이슈 트래커입니다.

11 저자

12 각주

1 또는, Wikipedia를 인용하면: "국제화는 엔지니어링 변경 없이 다양한 언어와 지역에 적응할 수 있도록 소프트웨어 애플리케이션을 설계하는 과정입니다. 지역화는 로케일별 구성 요소를 추가하고 텍스트를 번역하여 특정 지역이나 언어에 맞게 소프트웨어를 적응시키는 과정입니다."

2 다른 백엔드는 다른 형식을 사용하도록 허용하거나 요구할 수 있습니다. 예를 들어 GetText 백엔드는 GetText 파일을 읽을 수 있습니다.

3 이러한 이유 중 하나는 I18n 기능이 필요하지 않은 애플리케이션에 불필요한 로드를 주지 않으려고 하기 때문에 영어의 경우 I18n 라이브러리를 최대한 단순하게 유지해야 합니다. 또 다른 이유는 모든 기존 언어에 대한 I18n 관련 모든 문제에 대해 만능 솔루션을 구현하는 것이 사실상 불가능하기 때문입니다. 따라서 전체 구현을 쉽게 교체할 수 있는 솔루션이 적절합니다. 이는 또한 사용자 정의 기능과 확장을 실험하기가 훨씬 쉽게 만듭니다.



맨 위로