Overview
Rails GraphQL provides comprehensive internationalization (i18n) support for documenting your API. While field and type names cannot be translated, all descriptions can be provided in multiple languages using Rails’ I18n system.
Performance Impact : I18n is a heavy process. Enable only when delivering API documentation or in development mode.
Basic Concept
YAML-based translations for GraphQL components:
English (en.yml)
Spanish (es.yml)
en :
graphql :
field :
query :
hello : "Returns a greeting message"
object :
user : "Represents a user in the system"
Translations are retrieved during introspection or when generating documentation.
I18n Scopes
The gem uses configurable scopes to find translations:
# From docs/guides/i18n.md - default configuration
config. i18n_scopes = [
'graphql.%{namespace}.%{kind}.%{parent}.%{name}' ,
'graphql.%{namespace}.%{kind}.%{name}' ,
'graphql.%{namespace}.%{name}' ,
'graphql.%{kind}.%{parent}.%{name}' ,
'graphql.%{kind}.%{name}' ,
'graphql.%{name}' ,
]
Scope Variables
The namespace of the component (e.g., base, admin, public)
Component type: scalar, object, interface, union, enum, input_object, schema, field, argument, or directive
Parent component name (e.g., type name for fields, field name for arguments)
Symbolized component name
Translation Examples
Schema Translation
Given this schema:
# app/graphql/sample_schema.rb
module GraphQL
class SampleSchema < GraphQL::Schema
namespace :sample
object 'User' do
field ( :name )
end
query_fields do
field ( :hello ) { argument ( :world ) }
end
end
end
Schema Description
Scope order checked:
[
"graphql.sample.schema.schema" ,
"graphql.sample.schema" ,
"graphql.schema.schema" ,
"graphql.schema" ,
]
en :
graphql :
sample :
schema : "Sample API Schema"
Field Description
Variables: namespace: "sample", kind: "field", parent: "query", name: "hello"
Scope order:
[
"graphql.sample.field.query.hello" ,
"graphql.sample.field.hello" ,
"graphql.sample.hello" ,
"graphql.field.query.hello" ,
"graphql.field.hello" ,
"graphql.hello" ,
]
Specific to Query
Reusable Across Fields
en :
graphql :
sample :
field :
query :
hello : "Returns a hello message"
Argument Description
Variables: namespace: "sample", kind: "argument", parent: "hello", name: "world"
Scope order:
[
"graphql.sample.argument.hello.world" ,
"graphql.sample.argument.world" ,
"graphql.sample.world" ,
"graphql.argument.hello.world" ,
"graphql.argument.world" ,
"graphql.world" ,
]
en :
graphql :
argument :
hello :
world : "The world to greet"
Object Description
Variables: namespace: "sample", kind: "object", parent: nil, name: "user"
Scope order:
[
"graphql.sample.object.user" ,
"graphql.sample.user" ,
"graphql.object.user" ,
"graphql.user" ,
]
en :
graphql :
sample :
object :
user : "A user in the system"
field :
user :
name : "User's full name"
Object Field Description
Variables: namespace: "sample", kind: "field", parent: "user", name: "name"
Scope order:
[
"graphql.sample.field.user.name" ,
"graphql.sample.field.name" ,
"graphql.sample.name" ,
"graphql.field.user.name" ,
"graphql.field.name" ,
"graphql.name" ,
]
Complete Example
Schema Definition
English Translations
Spanish Translations
module GraphQL
class BlogSchema < GraphQL::Schema
namespace :blog
object 'Post' do
field :id , :id
field :title , :string
field :body , :string
field :published , :boolean
end
query_fields do
field ( :posts , 'Post' , array: true ) do
argument :published , :boolean , null: true
end
field ( :post , 'Post' ) do
argument :id , :id , null: false
end
end
end
end
Dynamic Translations
Use interpolation for dynamic content:
With Interpolation
In Code
en :
graphql :
field :
user :
posts : "Posts by %{user_name}"
Namespace-Specific Translations
Separate translations for different API versions:
en :
graphql :
v1 :
field :
query :
users : "List users (deprecated)"
v2 :
field :
query :
users : "List users with pagination"
Configuration
Custom Scopes
Modify the scope resolution order:
# config/initializers/graphql.rb
Rails :: GraphQL . config . i18n_scopes = [
'api.graphql.%{namespace}.%{kind}.%{name}' ,
'api.graphql.%{kind}.%{name}' ,
'api.graphql.%{name}' ,
]
Conditional Enabling
Enable i18n only when needed:
# config/initializers/graphql.rb
Rails :: GraphQL . config . enable_i18n = Rails . env . development? || ENV [ 'GRAPHQL_DOCS' ]
Collision Handling
Non-string values are skipped:
# This will be skipped
en :
graphql :
field :
user :
metadata :
key : "value" # Hash, not String
If a scope returns anything other than a plain String, that key is skipped and the next scope is checked.
Best Practices
Organize by Namespace Keep namespace translations in separate files: config/locales/
graphql/
en/
base.yml
admin.yml
public.yml
Use Fallbacks Structure translations from specific to general: en :
graphql :
# Specific
admin :
field :
query :
users : "Admin user list"
# General fallback
field :
query :
users : "User list"
Document in YAML Use YAML for documentation instead of code: # Instead of this:
field ( :name , :string , desc: "User's name" )
# Do this:
field ( :name , :string )
# Description in config/locales/graphql/en.yml
Test Translations Verify translations are loaded: I18n . with_locale ( :es ) do
description = GraphQL :: BlogSchema [ :query ][ :posts ]. description
assert_equal "Listar todas las publicaciones" , description
end
Cache Translations Cache description lookups: def description ( locale = I18n . locale )
@descriptions ||= {}
@descriptions [locale] ||= I18n . t (i18n_key, default: @description )
end
Eager Load Preload translations for introspection: # Before introspection
I18n . backend . eager_load!
Type Map How types are resolved for i18n
Introspection Using i18n with introspection