# General Translation Knowledge Base: General Translation vs. Mobility: which should you use?
URL: https://generaltranslation.com/en-US/kb/general-translation-vs-mobility.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Mobility stores translated model attributes in your database. General Translation produces those translations through its API, using your Glossary and Custom Prompts. Use both to translate product listings, posts, and other database content while keeping your Rails models.
Category: Internationalization
Last updated: 2026-10-16
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-16

Mobility is a Ruby gem for translating the data in your database: you declare `translates :title` on a model, and `post.title` returns the title in the current locale, read from whichever storage backend you choose. It stores and retrieves translations; writing them is up to your editors, translators, or a service. General Translation supplies them through its HTTP API. Mobility keeps the translated attributes in your existing database, ready for the same queries and model methods you use today.

## What Mobility does well

- **A choice of storage.** The default `key_value` backend [keeps every translation in two shared tables](https://github.com/shioyama/mobility/wiki/KeyValue-Backend) (one for strings, one for text). The `table` backend uses a translation table per model (the Globalize approach), `column` uses suffixed columns such as `title_en` and `title_es`, `serialized` keeps them in one text column, and on PostgreSQL `json`, `jsonb`, `hstore`, and `container` (one jsonb column per model) store them natively. You can also write your own backend.
- **One API across backends.** Reads and writes follow `I18n.locale`, and the plugins (fallbacks, locale accessors such as `post.title_es`, dirty tracking, caching, and presence checks) [work the same whichever backend you pick](https://github.com/shioyama/mobility#backends).
- **Querying.** [`Post.i18n.where(title: "...")`](https://github.com/shioyama/mobility#querying) builds the joins for the current locale, so you can search translated attributes without knowing how they're stored.
- **ORM support.** It [works with Active Record 7.0 and later and with Sequel](https://github.com/shioyama/mobility#installation), and its wiki has a guide for migrating from Globalize.
- **Free.** It's MIT-licensed; the [current release is 1.3.2](https://rubygems.org/gems/mobility).

## What Mobility leaves to you

Here's a post with an English title and body, using the `key_value` backend on SQLite, with a fallback from Spanish to English for the title only:

```ruby title="app/models/post.rb"
class Post < ApplicationRecord
  extend Mobility
  translates :title, type: :string, fallbacks: { es: :en }
  translates :body,  type: :text
end
```

```ruby
post = Post.create!(title: "Spring sale", body: "Everything is 20% off this week.")

I18n.with_locale(:es) { post.title } # => "Spring sale" (English, from the fallback)
I18n.with_locale(:es) { post.body }  # => nil
I18n.with_locale(:ja) { post.title } # => nil
```

That's the right behavior for a storage layer: Mobility saved the English values, and every other locale is empty until someone writes it. With a fallback, Spanish readers see English; without one, they see nothing. For content that changes every day, such as product listings, articles, or help pages, somebody has to translate each new or edited record into each language and save it under the right locale.

## Filling Mobility attributes with General Translation

General Translation's runtime translation endpoint, `POST https://api.gtx.dev/v2/translate`, translates strings on demand. It takes a project API key with **Runtime translation** permission as a bearer token, and a body with a `requests` object keyed by your own IDs, each with a `source` string and optional `metadata` such as a `context` note, plus `sourceLocale`, `targetLocale`, and a top-level `metadata` object (it can be empty). The response is keyed by the same IDs. A small Ruby method covers it:

```ruby title="app/services/gt_translate.rb"
require "net/http"
require "json"

# Translates a hash of { field => text } with General Translation's runtime API.
# Returns every requested translation, or raises before any database write.
def gt_translate(fields, from:, to:, context: nil)
  uri = URI("https://api.gtx.dev/v2/translate")
  requests = fields.to_h do |field, text|
    [field.to_s, { source: text, metadata: { dataFormat: "STRING", context: context }.compact }]
  end

  request = Net::HTTP::Post.new(uri)
  request["Authorization"] = "Bearer #{ENV.fetch("GT_API_KEY")}" # project key with Runtime translation permission
  request["Content-Type"] = "application/json"
  request["gt-api-version"] = "2026-03-06.v1"
  request.body = { requests:, sourceLocale: from.to_s, targetLocale: to.to_s, metadata: {} }.to_json

  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
                            open_timeout: 5, read_timeout: 30) { |http| http.request(request) }
  raise "Translation request failed (#{response.code})" unless response.is_a?(Net::HTTPSuccess)

  results = JSON.parse(response.body)
  requests.keys.to_h do |field|
    result = results.fetch(field)
    unless result["success"] == true && result["translation"].is_a?(String)
      raise "Translation incomplete for #{field}"
    end
    [field, result.fetch("translation")]
  end
end
```

For a one-off translation of the post above, read its English attributes and save the complete Spanish result:

```ruby
source = Mobility.with_locale(:en) do
  { title: post.title(fallback: false), body: post.body(fallback: false) }
end
translated = gt_translate(source,
                          from: :en, to: :es, context: "Blog post for an online shoe store")
Mobility.with_locale(:es) { post.update!(translated) }
```

The helper checks that every requested field has a translation before updating the post. Your model's normal validations still apply. (See the [endpoint reference](/docs/platform/openapi/reference/translation/translate-runtime) for request and response fields.)

General Translation does more here than a raw machine translation call: the endpoint translates with your project's [Glossary and Custom Prompts](/docs/platform/dashboard/guides/defining-context-for-translations), so product names and tone match the rest of your product. The context note tells it that the text is for a shoe store; shared terminology keeps those product descriptions consistent with your app and marketing site. Keep the API key on the server. For ongoing automation, connect translation to your existing content publication workflow and follow the API's [rate limits](/docs/platform/openapi/overview#api-basics).

The example saves directly to the model. For content your editors approve before publication, save the result through the same draft and publishing process as the source article.

## Mobility vs. General Translation at a glance

| Feature | Mobility | General Translation |
| --- | --- | --- |
| What it is | Ruby gem for storing translated model attributes | Translation platform with an HTTP API, CLI, libraries for JavaScript and Python, and a Dashboard |
| Stores translations in your database | Yes, with eight storage backends | Supplies translations to those same model attributes |
| Produces translations | No | Yes, through `POST /v2/translate` |
| Ruby integration | Model attributes and locale accessors | HTTP API called from Ruby |
| Consistent terms and tone | Up to your translators | Glossary and Custom Prompts applied to every request |
| Cost | Free, MIT | [Usage-based](https://generaltranslation.com/pricing) |

## When Mobility alone is enough

Keep Mobility on its own when people already write your translations: editors who enter each language in an admin form, a translation agency, or content such as legal text that must be translated by hand. It's also enough when you support two or three languages and records change rarely. Add General Translation when content arrives faster than people can translate it, such as user listings, support articles, or a catalog with thousands of products, and you want each new record in every language as soon as it's saved.

## Key takeaways

- Mobility stores and retrieves translated model attributes across eight backends, with fallbacks and querying. It doesn't produce translations.
- A Rails app calls General Translation's `POST /v2/translate` endpoint and saves the translated attributes through Mobility.
- General Translation applies your Glossary, Custom Prompts, and content-specific context while your existing models and publication workflow stay in place.

## Related questions

- [General Translation vs. Rails I18n: do you need both?](https://generaltranslation.com/en-US/kb/general-translation-vs-rails-i18n.mdx): Rails I18n looks up messages and formats dates and numbers. General Translation produces the translations, keeps them current as your English changes, and gives your team a Dashboard for review. Use both to ship translated Rails catalogs with each release.
- [General Translation vs. i18n-tasks: which should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-i18n-tasks.mdx): i18n-tasks is a Ruby gem that finds missing and unused Rails I18n keys, tidies locale files, and can fill missing keys through Google Translate, DeepL, OpenAI, Yandex, or watsonx. General Translation translates whole locale files with your Glossary, Custom Prompts, and Dashboard review. They do different jobs, and many teams can use both.
- [Why use General Translation instead of Google Translate?](https://generaltranslation.com/en-US/kb/general-translation-vs-google-translate.mdx): Google Translate is a quick way to translate one-off text. Shipping a product in another language also needs each string translated in context, consistent terms, correct variables and plurals, review, and a release process. General Translation understands each string's context in your code and automates the whole localization workflow, from code to review to every release.
- [General Translation vs. i18next, react-intl, and next-intl: which i18n library should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-i18n-libraries.mdx): Open-source libraries such as i18next, react-intl, and next-intl don't translate anything. They display translations you supply in a catalog file for each language, written by you, your translators, or a separate service. General Translation's libraries are open source too and do the same job, and General Translation also does the translating, with your code as context.
- [Why use General Translation instead of a TMS (Smartling, Lokalise, Phrase, Crowdin, or Transifex)?](https://generaltranslation.com/en-US/kb/general-translation-vs-tms.mdx): A translation management system (TMS) such as Smartling, Lokalise, Phrase, Crowdin, or Transifex coordinates translators, but engineers still extract strings, send them out, wait for translations, and wire them back into the product. General Translation translates content in your code with its context, ships every language with each release, and runs on open-source libraries with no lock-in.

## Previous and next questions

- Previous question: [General Translation vs. Lokalise: which should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-lokalise.mdx)
- Next question: [General Translation vs. nestjs-i18n: which should you use?](https://generaltranslation.com/en-US/kb/general-translation-vs-nestjs-i18n.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/kb/sitemap.md) for every article.
