Lingo.dev CLI는 설정된 config/locales로컬라이제이션 엔진을 통해 Rails YAML 파일을 번역합니다. Rails에는 i18n API가 기본으로 내장되어 있어, 앱에서 번역 가능한 텍스트는 로캘별 YAML 파일에 저장됩니다. Lingo.dev는 런타임 의존성을 추가하지 않고도 기존 파이프라인에 자연스럽게 녹아듭니다.
이 가이드는 Rails 앱을 처음부터 끝까지 로컬라이즈하는 전 과정을 안내합니다. CLI 설정, 로캘별 YAML 파일 구성, 요청 시점의 로캘 전환, 그리고 CI에서의 번역 자동화까지 모두 다룹니다.
데모 리포지토리
함께 따라 하려면 lingodotdev/ruby-on-rails-localization-example를 클론하거나 포크하세요. 실제로 동작하는 Rails 앱이며, config/locales YAML 파일과 커밋된 .lingo/config.json, 그리고 이미 적용된 번역까지 들어 있어 생성된 결과와 설정을 나란히 확인할 수 있습니다.
Rails 로컬라이제이션 작동 방식#
Rails는 config/locales/ 아래에 있는 YAML 파일에서 번역을 읽어옵니다. 각 파일은 루트에 로캘 코드가 키로 들어가며, 코드에서 I18n.t로 참조하는 조회 경로와 동일한 구조의 중첩 키를 포함합니다.
| 레이어 | 포함되는 내용 | 예시 파일 |
|---|---|---|
| UI 문자열 | 버튼, 레이블, 플래시 메시지 | config/locales/en.yml |
| 메일러 문구 | ActionMailer의 제목과 본문 | config/locales/mailers.en.yml |
| 모델 오류 | 유효성 검사 메시지와 속성 이름 | config/locales/activerecord.en.yml |
모든 Rails YAML 파일의 첫 번째 키는 로캘 코드 자체입니다 — en:, es:, fr:. Rails는 파일명이 아니라 이 루트 키를 기준으로 번역을 관리합니다. config/locales/ 아래의 모든 파일을 로드한 뒤, 각 파일의 내용을 파일이 선언한 루트 아래에 저장합니다. 그래서 es.yml가 여전히 en:를 루트로 가지고 있으면 무시되는 것이 아니라 en 네임스페이스에 병합됩니다. 결국 스페인어에는 번역이 하나도 생기지 않고, 영어 번역만 조용히 덮어써지게 됩니다.
즉, 이 형식을 번역한다는 것은 값만 옮기는 것이 아니라 그 키 자체까지 다시 써야 한다는 뜻입니다. yaml-root-key 형식은 바로 이 작업을 수행합니다. 루트 키 아래 트리를 순회하면서 문자열 값만 번역하고, 대상 로캘을 루트로 하는 파일로 저장합니다. 중첩 키, %{name} 치환 토큰, CLDR 복수 범주(zero/one/two/few/many/other)는 구조이므로 그대로 유지되며, 주석과 YAML 앵커도 손대지 않습니다.
사전 준비 사항#
로컬라이제이션 엔진 만들기
모든 CLI 실행 시 콘텐츠는 어떤 LLM 모델과 용어집, 브랜드 보이스, 규칙을 적용할지 결정하는 설정인 로컬라이제이션 엔진을 거쳐 전송됩니다. Lingo.dev dashboard에서 로컬라이제이션 엔진을 만들고 API key를 생성하세요.
Ruby와 Rails 확인
이 가이드는 Rails 7.2 이상을 기준으로 하며, Ruby 3.1 이상이 필요합니다. 버전을 확인하세요:
ruby -v
rails -vNode.js 확인
CLI를 사용하려면 Node.js 22 이상이 필요합니다:
node -vRails i18n 설정
이 가이드는 앱이 이미 번역 문자열을 config/locales/*.yml에 저장하고 있다고 가정합니다. 뷰나 컨트롤러에 하드코딩된 문자열이 있다면 먼저 t() 호출로 추출하세요. 예를 들어, 다음 코드를:
<h1>Welcome</h1>다음처럼 바꿉니다:
<h1><%= t(".welcome") %></h1>그런 다음 해당 키를 config/locales/en.yml에 추가하세요. 전체 마이그레이션 절차는 Rails internationalization guide를 참고하세요.
번역 파일 구성하기#
Rails는 *.yml 아래의 모든 config/locales/ 파일을 자동으로 로드합니다. 소스 로캘 파일을 번역된 파일들과 나란히 두면, 이 디렉터리가 단일 진실 공급원 역할을 하게 됩니다:
config/locales/
en.yml # Source locale
es.yml # Generated by Lingo.dev
fr.yml
de.yml일반적인 en.yml 파일에는 일반 문자열, 중첩 네임스페이스, %{name} 보간, 복수형이 함께 들어 있습니다:
en:
hello: "Hello"
home:
welcome: "Welcome, %{name}!"
cta: "Get started"
notifications:
unread:
zero: "No unread notifications"
one: "1 unread notification"
other: "%{count} unread notifications"
errors:
messages:
blank: "can't be blank"CLI 설정하기#
CLI를 설치하고, 인증한 다음, 프로젝트를 엔진에 연결하세요:
npm install -g @lingo.dev/cli
lingo login
lingo init
lingo linklingo init 및 lingo link를 실행하면 사용 중인 로캘과 엔진의 .lingo/config.json, orgId가 담긴 engineId가 생성됩니다. 모든 머신과 모든 CI 실행이 같은 상태를 공유할 수 있도록 .lingo/lock.json와 함께 커밋해 두세요.
files[] 항목이 소스 로캘 파일을 가리키도록 하고, 형식도 명시적으로 지정하세요:
{
"orgId": "org_...",
"engineId": "eng_...",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de"],
"files": [{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" }]
}여기서는 "format": "yaml-root-key"가 선택이 아니라 필수입니다. .yml 경로만으로는 루트 키가 로캘인지 일반 설정인지 알 수 없기 때문에 CLI는 추측하지 않습니다. format를 생략하면 파일은 일반 yaml로 처리되어 값만 번역되고 루트는 en:로 남습니다. 바로 위에서 설명한 그 조용한 실패가 발생하는 셈입니다.
대상 파일은 소스 경로를 기준으로 파생되므로 config/locales/en.yml는 config/locales/es.yml, config/locales/fr.yml, config/locales/de.yml를 생성합니다.
Rails는 en.yml와 함께 관심사별 파일도 로드합니다 — devise.en.yml, mailers.en.yml, activerecord.en.yml. 이 파일들까지 포함하려면 glob을 사용하는 두 번째 항목을 추가하세요:
{
"files": [
{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" },
{ "pattern": "config/locales/*.en.yml", "format": "yaml-root-key" }
]
}두 패턴은 서로 겹치지 않습니다. 첫 번째는 en.yml에만 매칭되고, 두 번째는 .en.yml로 끝나는 파일에만 매칭되므로 es.yml나 devise.es.yml처럼 이미 번역된 파일이 소스로 다시 잡히는 일은 없습니다. devise.en.yml는 devise.es.yml를 생성합니다.
여러 로캘에 맞게 Rails 설정하기#
사용 가능한 로캘과 기본 로캘을 Rails에 알려주세요. config/application.rb에서 설정합니다:
module YourApp
class Application < Rails::Application
config.i18n.available_locales = [:en, :es, :fr, :de]
config.i18n.default_locale = :en
config.i18n.fallbacks = [:en]
end
endURL 파라미터 또는 ApplicationController 헤더를 기준으로 Accept-Language에서 요청 로캘을 선택하세요:
class ApplicationController < ActionController::Base
around_action :switch_locale
private
def switch_locale(&action)
locale = params[:locale] || http_accept_locale || I18n.default_locale
I18n.with_locale(locale, &action)
end
def http_accept_locale
header = request.headers["Accept-Language"].to_s
header.scan(/[a-z]{2}/).find { |l| I18n.available_locales.map(&:to_s).include?(l) }
end
def default_url_options
{ locale: I18n.locale }
end
end뷰에서 번역 렌더링하기#
ERB 템플릿에서는 t 및 l 헬퍼를 사용하세요. 키 앞에 점을 붙이면 현재 뷰 경로를 기준으로 해석되므로, 번역 키를 해당 템플릿 가까이에 둘 수 있습니다:
<h1><%= t(".welcome", name: @user_name) %></h1>
<p><%= t("notifications.unread", count: @unread_count) %></p>
<%= link_to t(".cta"), signup_path, class: "btn-primary" %>레이아웃에 로캘 전환기를 추가하세요:
<nav>
<% I18n.available_locales.each do |locale| %>
<%= link_to locale.upcase, url_for(locale: locale) %>
<% end %>
</nav>로컬에서 번역하기#
lingo push --waitpush는 소스 파일을 업로드해 로컬라이제이션 엔진으로 처리한 뒤, 번역된 파일을 다시 기록합니다. --wait를 사용하면 결과물이 준비될 때까지 명령이 블로킹 상태로 유지됩니다. 기본 동작이 바뀔 예정인 릴리스가 있어 이 옵션은 명시적으로 넣어두는 편이 좋습니다. 변경 이후에는 push가 실행만 제출하고 바로 반환하므로, 결과는 lingo pull로 직접 가져와야 합니다.
이후 실행에서는 변경된 부분만 번역합니다. push가 소스를 해시해 .lingo/lock.json와 비교하므로 바뀌지 않은 항목에는 비용이 들지 않습니다. 프로젝트에서의 첫 실행이나 로캘을 새로 추가한 직후에는 전체 코퍼스를 처리해야 합니다:
lingo push --backfill-missingglob을 전달해 실행 범위를 일부 파일로 제한할 수도 있습니다:
lingo push "config/locales/**"다른 곳에서 생성된 결과물을 가져오려면 — 예를 들어 다른 머신이나 CI에서 생성한 경우 — lingo pull를 실행하세요.
새 YAML 파일이 로드되도록 첫 번역 실행 후 Rails 서버를 다시 시작하세요:
bin/rails server스페인어 결과를 확인하려면 /es로 이동하세요.
복수형#
Rails는 CLDR plural categories를 사용합니다. 즉, zero, one, two, few, many, other입니다. count:에 I18n.t 인수를 넘기면 Rails가 해당하는 키를 선택합니다:
t("notifications.unread", count: 0) # => "No unread notifications"
t("notifications.unread", count: 1) # => "1 unread notification"
t("notifications.unread", count: 12) # => "12 unread notifications"CLI는 각 복수형 변형을 해당 위치에서 그대로 번역합니다. 대상 로캘에 영어의 one/other보다 더 많은 범주가 필요하다면, 소스 en.yml에 미리 정의해두세요.
CI에서 자동화하기#
Lingo.dev GitHub App은 모든 푸시와 풀 리퀘스트마다 서버 측에서 번역을 수행합니다. 러너도 필요 없고, 저장소에 API 키를 보관할 필요도 없습니다. 커밋된 .lingo/config.json를 기준으로 엔진을 확인하므로, 커밋하는 파일에 orgId와 engineId가 반드시 들어 있어야 합니다.
대신 GitHub Actions, GitLab CI, Bitbucket Pipelines 같은 자체 파이프라인에서 CLI를 실행하고 싶다면 CI/CD 워크플로를 참고하세요. LINGO_API_KEY를 시크릿으로 제공하고 lingo push --wait를 다른 빌드 단계처럼 호출하면 됩니다.
배포 전 검증#
번역되지 않은 콘텐츠가 프로덕션에 배포되지 않도록 lingo check를 배포 게이트로 활용하세요. 아직 번역이 필요한 항목이 하나라도 있으면 0이 아닌 상태 코드로 종료하고, 아무것도 기록하지 않습니다:
lingo check에셋 프리컴파일이나 컨테이너 빌드 전에 이 명령을 별도의 CI 단계로 추가하세요:
- name: Verify translations
run: lingo check
- name: Precompile assets
run: bundle exec rails assets:precompile