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.rb
의 root 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>
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는 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을 사용하고 응답은 영어 문자열을 렌더링합니다:
locale이 URL을 통해 pirate locale로 설정된 경우(http://localhost:3000?locale=pirate
), 응답은 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} €"
모든 문법과 구두점에 대한 결정은 정의 자체에서 이루어지므로, 추상화를 통해 적절한 번역을 제공할 수 있습니다.
default
와 scope
키워드는 예약어이므로 변수명으로 사용할 수 없습니다. 만약 사용하면 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"
그러면 다음과 같은 결과를 얻을 수 있습니다:
현재 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.locale
은 I18n.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에서 작동합니다.
4.5 Active Record Model에 대한 번역
Model.model_name.human
과 Model.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
을 포함하는 클래스를 사용하는 경우, 위의 키 경로에서 activerecord
를 activemodel
로 교체하세요.
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_select
와select_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.human
과human_attribute_name
은 activerecord.models 범위에서 사용 가능한 경우 모델 이름과 속성 이름에 대한 번역을 사용합니다. 또한 위의 "Error message scopes"에서 설명한 대로 상속된 클래스 이름에 대한 번역도 지원합니다(예: STI와 함께 사용).ActiveModel::Errors#generate_message
(Active Model validation에서 사용되지만 수동으로도 사용 가능)는model_name.human
과human_attribute_name
을 사용합니다(위 참조). 또한 에러 메시지를 번역하고 "Error message scopes"에서 설명한 대로 상속된 클래스 이름에 대한 번역을 지원합니다.ActiveModel::Error#full_message
와ActiveModel::Errors#full_messages
는errors.format
에서 찾은 format을 사용하여 속성 이름을 에러 메시지 앞에 추가합니다(기본값:"%{attribute} %{message}"
). 기본 format을 커스터마이징하려면 앱의 locale 파일에서 이를 재정의하세요. 모델별 또는 속성별로 format을 커스터마이징하려면 [config.active_model.i18n_customize_full_message
][]를 참조하세요.
4.7.3 Active Support 메서드
Array#to_sentence
는 support.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_translations
가 true
이면, view와 controller에서 I18n::MissingTranslationData
에러가 발생합니다. 값이 :strict
이면 model에서도 에러가 발생합니다. 누락된 번역이 요청되는 곳을 찾을 수 있도록 테스트 환경에서 이 옵션을 활성화하는 것이 좋습니다.
config.i18n.raise_on_missing_translations
가 false
이면 (모든 환경에서의 기본값), 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 저자
- Sven Fuchs (최초 저자)
- Karel Minařík
12 각주
1 또는, Wikipedia를 인용하면: "국제화는 엔지니어링 변경 없이 다양한 언어와 지역에 적응할 수 있도록 소프트웨어 애플리케이션을 설계하는 과정입니다. 지역화는 로케일별 구성 요소를 추가하고 텍스트를 번역하여 특정 지역이나 언어에 맞게 소프트웨어를 적응시키는 과정입니다."
2 다른 백엔드는 다른 형식을 사용하도록 허용하거나 요구할 수 있습니다. 예를 들어 GetText 백엔드는 GetText 파일을 읽을 수 있습니다.
3 이러한 이유 중 하나는 I18n 기능이 필요하지 않은 애플리케이션에 불필요한 로드를 주지 않으려고 하기 때문에 영어의 경우 I18n 라이브러리를 최대한 단순하게 유지해야 합니다. 또 다른 이유는 모든 기존 언어에 대한 I18n 관련 모든 문제에 대해 만능 솔루션을 구현하는 것이 사실상 불가능하기 때문입니다. 따라서 전체 구현을 쉽게 교체할 수 있는 솔루션이 적절합니다. 이는 또한 사용자 정의 기능과 확장을 실험하기가 훨씬 쉽게 만듭니다.