{"id":13506938,"url":"https://github.com/shioyama/mobility","last_synced_at":"2025-10-17T14:36:07.603Z","repository":{"id":39420968,"uuid":"82801745","full_name":"shioyama/mobility","owner":"shioyama","description":"Pluggable Ruby translation framework","archived":false,"fork":false,"pushed_at":"2025-02-01T08:23:05.000Z","size":2984,"stargazers_count":1058,"open_issues_count":23,"forks_count":88,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-05-06T17:58:14.486Z","etag":null,"topics":["activerecord","hstore","i18n","json","jsonb","localization","postgresql","rails","ruby","ruby-gem","sequel","translation"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shioyama.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-22T12:27:51.000Z","updated_at":"2025-05-05T12:30:11.000Z","dependencies_parsed_at":"2022-07-14T10:48:40.908Z","dependency_job_id":"637c850a-5c87-4f3f-b172-277129ec0a3b","html_url":"https://github.com/shioyama/mobility","commit_stats":{"total_commits":1527,"total_committers":39,"mean_commits":39.15384615384615,"dds":0.05239030779305831,"last_synced_commit":"30994db1150f15c8299566c23fe87ff7b59ab7be"},"previous_names":[],"tags_count":91,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shioyama%2Fmobility","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shioyama%2Fmobility/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shioyama%2Fmobility/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shioyama%2Fmobility/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shioyama","download_url":"https://codeload.github.com/shioyama/mobility/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990500,"owners_count":21995776,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","hstore","i18n","json","jsonb","localization","postgresql","rails","ruby","ruby-gem","sequel","translation"],"created_at":"2024-08-01T01:01:00.570Z","updated_at":"2025-10-17T14:36:02.559Z","avatar_url":"https://github.com/shioyama.png","language":"Ruby","funding_links":[],"categories":["Uncategorized","Ruby"],"sub_categories":["Uncategorized"],"readme":"Mobility\n========\n\n[![Gem Version](https://badge.fury.io/rb/mobility.svg)][gem]\n[![Build Status](https://github.com/shioyama/mobility/workflows/CI/badge.svg)][actions]\n[![Code Climate](https://api.codeclimate.com/v1/badges/72200f2b00c339ec4537/maintainability.svg)][codeclimate]\n\n[gem]: https://rubygems.org/gems/mobility\n[actions]: https://github.com/shioyama/mobility/actions\n[codeclimate]: https://codeclimate.com/github/shioyama/mobility\n[docs]: http://www.rubydoc.info/gems/mobility\n[wiki]: https://github.com/shioyama/mobility/wiki\n\n**This is the readme for version 1.x of Mobility. If you are using an earlier\nversion (0.8.x or earlier), you probably want the readme on the [0-8\nbranch](https://github.com/shioyama/mobility/tree/0-8).**\n\nMobility is a gem for storing and retrieving translations as attributes on a\nclass. These translations could be the content of blog posts, captions on\nimages, tags on bookmarks, or anything else you might want to store in\ndifferent languages. For examples of what Mobility can do, see the\n\u003ca href=\"#companies-using-mobility\"\u003eCompanies using Mobility\u003c/a\u003e section below.\n\nStorage of translations is handled by customizable \"backends\" which encapsulate\ndifferent storage strategies. The default way to store translations\nis to put them all in a set of two shared tables, but many alternatives are\nalso supported, including [translatable\ncolumns](http://dejimata.com/2017/3/3/translating-with-mobility#strategy-1) and\n[model translation\ntables](http://dejimata.com/2017/3/3/translating-with-mobility#strategy-2), as\nwell as database-specific storage solutions such as\n[json/jsonb](https://www.postgresql.org/docs/current/static/datatype-json.html) and\n[Hstore](https://www.postgresql.org/docs/current/static/hstore.html) (for\nPostgreSQL).\n\nMobility is a cross-platform solution, currently supporting both\n[ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)\nand [Sequel](http://sequel.jeremyevans.net/) ORM, with support for other\nplatforms planned.\n\nFor a detailed introduction to Mobility, see [Translating with\nMobility](http://dejimata.com/2017/3/3/translating-with-mobility). See also my\ntalk at RubyConf 2018, [Building Generic\nSoftware](https://www.youtube.com/watch?v=RZkemV_-__A), where I explain the\nthinking behind Mobility's design.\n\nIf you're coming from Globalize, be sure to also read the [Migrating from\nGlobalize](https://github.com/shioyama/mobility/wiki/Migrating-from-Globalize)\nsection of the wiki.\n\nInstallation\n------------\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'mobility', '~\u003e 1.3.2'\n```\n\n### ActiveRecord (Rails)\n\nRequirements:\n- ActiveRecord \u003e= 7.0\n\nTo translate attributes on a model, extend `Mobility`, then call `translates`\npassing in one or more attributes as well as a hash of options (see below).\n\nIf using Mobility in a Rails project, you can run the generator to create an\ninitializer and a migration to create shared translation tables for the\ndefault `KeyValue` backend:\n\n```\nrails generate mobility:install\n```\n\n(If you do not plan to use the default backend, you may want to use\nthe `--without_tables` option here to skip the migration generation.)\n\nThe generator will create an initializer file `config/initializers/mobility.rb`\nwhich looks something like this:\n\n```ruby\nMobility.configure do\n\n  # PLUGINS\n  plugins do\n    backend :key_value\n\n    active_record\n\n    reader\n    writer\n\n    # ...\n  end\nend\n```\n\nEach method call inside the block passed to `plugins` declares a plugin, along\nwith an optional default. To use a different default backend, you can\nchange the default passed to the `backend` plugin, like this:\n\n```diff\n Mobility.configure do\n\n   # PLUGINS\n   plugins do\n-    backend :key_value\n+    backend :table\n```\n\nSee other possible backends in the [backends section](#backends).\n\nYou can also set defaults for backend-specific options. Below, we set the\ndefault `type` option for the KeyValue backend to `:string`.\n\n```diff\n Mobility.configure do\n\n   # PLUGINS\n   plugins do\n-    backend :key_value\n+    backend :key_value, type: :string\n   end\n end\n```\n\nWe will assume the configuration above in the examples that follow.\n\nSee [Getting Started](#quickstart) to get started translating your models.\n\n### Sequel\n\nRequirements:\n- Sequel \u003e= 4.0\n\nWhen configuring Mobility, ensure that you include the `sequel` plugin:\n\n```diff\n plugins do\n   backend :key_value\n\n-    active_record\n+    sequel\n```\n\nYou can extend `Mobility` just like in ActiveRecord, or you can use the\n`mobility` plugin, which does the same thing:\n\n```ruby\nclass Word \u003c ::Sequel::Model\n  plugin :mobility\n  translates :name, :meaning\nend\n```\n\nOtherwise everything is (almost) identical to AR, with the exception that there\nis no equivalent to a Rails generator, so you will need to create the migration\nfor any translation table(s) yourself, using Rails generators as a reference.\n\nThe models in examples below all inherit from `ApplicationRecord`, but\neverything works exactly the same if the parent class is `Sequel::Model`.\n\nUsage\n-----\n\n### \u003ca name=\"quickstart\"\u003e\u003c/a\u003eGetting Started\n\nOnce the install generator has been run to generate translation tables, using\nMobility is as easy as adding a few lines to any class you want to translate.\nSimply pass one or more attribute names to the `translates` method with a hash\nof options, like this:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name, :meaning\nend\n```\n\nNote: When using the KeyValue backend, use the options hash to pass each attribute's type:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name,    type: :string\n  translates :meaning, type: :text\nend\n```\n\nThis is important because this is how Mobility knows to which of the [two translation tables](https://github.com/shioyama/mobility/wiki/KeyValue-Backend) it should save your translation.\n\nYou now have translated attributes `name` and `meaning` on the model `Word`.\nYou can set their values like you would any other attribute:\n\n```ruby\nword = Word.new\nword.name = \"mobility\"\nword.meaning = \"(noun): quality of being changeable, adaptable or versatile\"\nword.name\n#=\u003e \"mobility\"\nword.meaning\n#=\u003e \"(noun): quality of being changeable, adaptable or versatile\"\nword.save\nword = Word.first\nword.name\n#=\u003e \"mobility\"\nword.meaning\n#=\u003e \"(noun): quality of being changeable, adaptable or versatile\"\n```\n\nPresence methods are also supported:\n\n```ruby\nword.name?\n#=\u003e true\nword.name = nil\nword.name?\n#=\u003e false\nword.name = \"\"\nword.name?\n#=\u003e false\n```\n\nWhat's different here is that the value of these attributes changes with the\nvalue of `I18n.locale`:\n\n```ruby\nI18n.locale = :ja\nword.name\n#=\u003e nil\nword.meaning\n#=\u003e nil\n```\n\nThe `name` and `meaning` of this word are not defined in any locale except\nEnglish. Let's define them in Japanese and save the model:\n\n```ruby\nword.name = \"モビリティ\"\nword.meaning = \"(名詞):動きやすさ、可動性\"\nword.name\n#=\u003e \"モビリティ\"\nword.meaning\n#=\u003e \"(名詞):動きやすさ、可動性\"\nword.save\n```\n\nNow our word has names and meanings in two different languages:\n\n```ruby\nword = Word.first\nI18n.locale = :en\nword.name\n#=\u003e \"mobility\"\nword.meaning\n#=\u003e \"(noun): quality of being changeable, adaptable or versatile\"\nI18n.locale = :ja\nword.name\n#=\u003e \"モビリティ\"\nword.meaning\n#=\u003e \"(名詞):動きやすさ、可動性\"\n```\n\nInternally, Mobility is mapping the values in different locales to storage\nlocations, usually database columns. By default these values are stored as keys\n(attribute names) and values (attribute translations) on a set of translation\ntables, one for strings and one for text columns, but this can be easily\nchanged and/or customized (see the [Backends](#backends) section below).\n\n### \u003ca name=\"getset\"\u003e\u003c/a\u003e Getting and Setting Translations\n\nThe easiest way to get or set a translation is to use the getter and setter\nmethods described above (`word.name` and `word.name=`), enabled by including\nthe `reader` and `writer` plugins.\n\nYou may also want to access the value of an attribute in a specific locale,\nindependent of the current value of `I18n.locale` (or `Mobility.locale`). There\nare a few ways to do this.\n\nThe first way is to define locale-specific methods, one for each locale you\nwant to access directly on a given attribute. These are called \"locale\naccessors\" in Mobility, and can be enabled by including the `locale_accessors`\nplugin, with a default set of accessors:\n\n```diff\n plugins do\n   # ...\n+  locale_accessors [:en, :ja]\n```\n\nYou can also override this default from `translates` in any model:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name, locale_accessors: [:en, :ja]\nend\n```\n\nSince we have enabled locale accessors for English and Japanese, we can access\ntranslations for these locales with `name_en` and `name_ja`:\n\n```ruby\nword.name_en\n#=\u003e \"mobility\"\nword.name_ja\n#=\u003e \"モビリティ\"\nword.name_en = \"foo\"\nword.name\n#=\u003e \"foo\"\n```\n\nOther locales, however, will not work:\n\n```ruby\nword.name_ru\n#=\u003e NoMethodError: undefined method `name_ru' for #\u003cWord id: ... \u003e\n```\n\nWith no plugin option (or a default of `true`), Mobility generates methods for\nall locales in `I18n.available_locales` at the time the model is first loaded.\n\nAn alternative to using the `locale_accessors` plugin is to use the\n`fallthrough_accessors` plugin. This uses Ruby's\n[`method_missing`](http://apidock.com/ruby/BasicObject/method_missing) method\nto implicitly define the same methods as above, but supporting any locale\nwithout any method definitions. (Locale accessors and fallthrough locales can\nbe used together without conflict, with locale accessors taking precedence if\ndefined for a given locale.)\n\nEnsure the plugin is enabled:\n\n```diff\n plugins do\n   # ...\n+  fallthrough_accessors\n```\n\n... then we can access any locale we want, without specifying them upfront:\n\n```ruby\nword = Word.new\nword.name_fr = \"mobilité\"\nword.name_fr\n#=\u003e \"mobilité\"\nword.name_ja = \"モビリティ\"\nword.name_ja\n#=\u003e \"モビリティ\"\n```\n\n(Note however that Mobility will complain if you have\n`I18n.enforce_available_locales` set to `true` and you try accessing a locale\nnot present in `I18n.available_locales`; set it to `false` if you want to allow\n*any* locale.)\n\nAnother way to fetch values in a locale is to pass the `locale` option to the\ngetter method, like this:\n\n```ruby\nword.name(locale: :en)\n#=\u003e \"mobility\"\nword.name(locale: :fr)\n#=\u003e \"mobilité\"\n```\n\nNote that setting the locale this way will pass an option `locale: true` to the\nbackend and all plugins. Plugins may use this option to change their behavior\n(passing the locale explicitly this way, for example, disables\n[fallbacks](#fallbacks), see below for details).\n\nYou can also *set* the value of an attribute this way; however, since the\n`word.name = \u003cvalue\u003e` syntax does not accept any options, the only way to do this is to\nuse `send` (this is included mostly for consistency):\n\n```ruby\nword.send(:name=, \"mobiliteit\", locale: :nl)\nword.name_nl\n#=\u003e \"mobiliteit\"\n```\n\nYet another way to get and set translated attributes is to call `read` and\n`write` on the storage backend, which can be accessed using the method\n`\u003cattribute\u003e_backend`. Without worrying too much about the details of\nhow this works for now, the syntax for doing this is simple:\n\n```ruby\nword.name_backend.read(:en)\n#=\u003e \"mobility\"\nword.name_backend.read(:nl)\n#=\u003e \"mobiliteit\"\nword.name_backend.write(:en, \"foo\")\nword.name_backend.read(:en)\n#=\u003e \"foo\"\n```\n\nInternally, all methods for accessing translated attributes ultimately end up\nreading and writing from the backend instance this way.  (The `write` methods\ndo not call underlying backend's methods to persist the change. This is up to\nthe user, so e.g. with ActiveRecord you should call `save` write the changes to\nthe database).\n\nNote that accessor methods are defined in an included module, so you can wrap\nreads or writes in custom logic:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  extend Mobility\n  translates :title\n\n  def title(*)\n    super.reverse\n  end\nend\n```\n\n### Setting the Locale\n\nIt may not always be desirable to use `I18n.locale` to set the locale for\ncontent translations. For example, a user whose interface is in English\n(`I18n.locale` is `:en`) may want to see content in Japanese. If you use\n`I18n.locale` exclusively for the locale, you will have a hard time showing\nstored translations in one language while showing the interface in another\nlanguage.\n\nFor these cases, Mobility also has its own locale, which defaults to\n`I18n.locale` but can be set independently:\n\n```ruby\nI18n.locale = :en\nMobility.locale              #=\u003e :en\nMobility.locale = :fr\nMobility.locale              #=\u003e :fr\nI18n.locale                  #=\u003e :en\n```\n\nTo set the Mobility locale in a block, you can use `Mobility.with_locale` (like\n`I18n.with_locale`):\n\n```ruby\nMobility.locale = :en\nMobility.with_locale(:ja) do\n  Mobility.locale            #=\u003e :ja\nend\nMobility.locale              #=\u003e :en\n```\n\nMobility uses [RequestStore](https://github.com/steveklabnik/request_store) to\nreset these global variables after every request, so you don't need to worry\nabout thread safety. If you're not using Rails, consult RequestStore's\n[README](https://github.com/steveklabnik/request_store#no-rails-no-problem) for\ndetails on how to configure it for your use case.\n\n### \u003ca name=\"fallbacks\"\u003e\u003c/a\u003eFallbacks\n\nMobility offers basic support for translation fallbacks. First, enable the\n`fallbacks` plugin:\n\n```diff\n plugins do\n   # ...\n+  fallbacks\n+  locale_accessors\n```\n\nFallbacks will require `fallthrough_accessors` to handle methods like\n`title_en`, which are used to track changes. For performance reasons it's\ngenerally best to also enable the `locale_accessors` plugin as shown above.\n\nNow pass a hash with fallbacks for each locale as an option when defining\ntranslated attributes on a class:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name,    fallbacks: { de: :ja, fr: :ja }\n  translates :meaning, fallbacks: { de: :ja, fr: :ja }\nend\n```\n\nInternally, Mobility assigns the fallbacks hash to an instance of\n`I18n::Locale::Fallbacks.new`.\n\nBy setting fallbacks for German and French to Japanese, values will fall\nthrough to the Japanese value if none is present for either of these locales,\nbut not for other locales:\n\n```ruby\nMobility.locale = :ja\nword = Word.create(name: \"モビリティ\", meaning: \"(名詞):動きやすさ、可動性\")\nMobility.locale = :de\nword.name\n#=\u003e \"モビリティ\"\nword.meaning\n#=\u003e \"(名詞):動きやすさ、可動性\"\nMobility.locale = :fr\nword.name\n#=\u003e \"モビリティ\"\nword.meaning\n#=\u003e \"(名詞):動きやすさ、可動性\"\nMobility.locale = :ru\nword.name\n#=\u003e nil\nword.meaning\n#=\u003e nil\n```\n\nYou can optionally disable fallbacks to get the real value for a given locale\n(for example, to check if a value in a particular locale is set or not) by\npassing `fallback: false` (*singular*, not plural) to the getter method:\n\n```ruby\nMobility.locale = :de\nword.meaning(fallback: false)\n#=\u003e nil\nMobility.locale = :fr\nword.meaning(fallback: false)\n#=\u003e nil\nMobility.locale = :ja\nword.meaning(fallback: false)\n#=\u003e \"(名詞):動きやすさ、可動性\"\n```\n\nYou can also set the fallback locales for a single read by passing one or more\nlocales:\n\n```ruby\nMobility.with_locale(:fr) do\n  word.meaning = \"(nf): aptitude à bouger, à se déplacer, à changer, à évoluer\"\nend\nword.save\nMobility.locale = :de\nword.meaning(fallback: false)\n#=\u003e nil\nword.meaning(fallback: :fr)\n#=\u003e \"(nf): aptitude à bouger, à se déplacer, à changer, à évoluer\"\nword.meaning(fallback: [:ja, :fr])\n#=\u003e \"(名詞):動きやすさ、可動性\"\n```\n\nAlso note that passing a `locale` option into an attribute reader or writer, or\nusing [locale accessors or fallthrough accessors](#getset) to get or set\nany attribute value, will disable fallbacks (just like `fallback: false`).\n(This will take precedence over any value of the `fallback` option.)\n\nContinuing from the last example:\n\n```ruby\nword.meaning(locale: :de)\n#=\u003e nil\nword.meaning_de\n#=\u003e nil\nMobility.with_locale(:de) { word.meaning }\n#=\u003e \"(名詞):動きやすさ、可動性\"\n```\n\nFor more details, see the [API documentation on\nfallbacks](http://www.rubydoc.info/gems/mobility/Mobility/Plugins/Fallbacks)\nand [this article on I18n\nfallbacks](https://github.com/svenfuchs/i18n/wiki/Fallbacks).\n\n### \u003ca name=\"default\"\u003e\u003c/a\u003eDefault values\n\nAnother option is to assign a default value, using the `default` plugin:\n\n```diff\n plugins do\n   # ...\n+  default 'foo'\n```\n\nHere we've set a \"default default\" of `'foo'`, which will be returned if a fetch would\notherwise return `nil`. This can be overridden from model classes:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name, default: 'foo'\nend\n\nMobility.locale = :ja\nword = Word.create(name: \"モビリティ\")\nword.name\n#=\u003e \"モビリティ\"\nMobility.locale = :de\nword.name\n#=\u003e \"foo\"\n```\n\nYou can override the default by passing a `default` option to the attribute reader:\n\n```ruby\nword.name\n#=\u003e 'foo'\nword.name(default: nil)\n#=\u003e nil\nword.name(default: 'bar')\n#=\u003e 'bar'\n```\n\nThe default can also be a `Proc`, which will be called with the context as the\nmodel itself, and passed optional arguments (attribute, locale and options\npassed to accessor) which can be used to customize behaviour. See the [API\ndocs][docs] for details.\n\n### \u003ca name=\"dirty\"\u003e\u003c/a\u003eDirty Tracking\n\nDirty tracking (tracking of changed attributes) can be enabled for models which\nsupport it. Currently this is models which include\n[ActiveModel::Dirty](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html)\n(like `ActiveRecord::Base`) and Sequel models (through the\n[dirty](http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/Dirty.html)\nplugin).\n\nFirst, ensure the `dirty` plugin is enabled in your configuration, and that you\nhave enabled an ORM plugin (either `active_record` or `sequel`), since the\ndirty plugin will depend on one of these being enabled.\n\n```diff\n plugins do\n   # ...\n   active_record\n+  dirty\n```\n\n(Once enabled globally, the dirty plugin can be selectively disabled on classes\nby passing `dirty: false` to `translates`.)\n\nTake this ActiveRecord class:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  extend Mobility\n  translates :title\nend\n```\n\nLet's assume we start with a post with a title in English and Japanese:\n\n```ruby\npost = Post.create(title: \"Introducing Mobility\")\nMobility.with_locale(:ja) { post.title = \"モビリティの紹介\" }\npost.save\n```\n\nNow let's change the title:\n\n```ruby\npost = Post.first\npost.title                      #=\u003e \"Introducing Mobility\"\npost.title = \"a new title\"\nMobility.with_locale(:ja) do\n  post.title                    #=\u003e \"モビリティの紹介\"\n  post.title = \"新しいタイトル\"\n  post.title                    #=\u003e \"新しいタイトル\"\nend\n```\n\nNow you can use dirty methods as you would any other (untranslated) attribute:\n\n```ruby\npost.title_was\n#=\u003e \"Introducing Mobility\"\nMobility.locale = :ja\npost.title_was\n#=\u003e \"モビリティの紹介\"\npost.changed\n[\"title_en\", \"title_ja\"]\npost.save\n```\n\nYou can also access `previous_changes`:\n\n```ruby\npost.previous_changes\n#=\u003e\n{\n  \"title_en\" =\u003e\n    [\n      \"Introducing Mobility\",\n      \"a new title\"\n    ],\n  \"title_ja\" =\u003e\n    [\n      \"モビリティの紹介\",\n      \"新しいタイトル\"\n    ]\n}\n```\n\nNotice that Mobility uses locale suffixes to indicate which locale has changed;\ndirty tracking is implemented this way to ensure that it is clear what\nhas changed in which locale, avoiding any possible ambiguity.\n\nFor performance reasons, it is highly recommended that when using the Dirty\nplugin, you also enable [locale accessors](#getset) for all locales which will\nbe used, so that methods like `title_en` above are defined; otherwise they will\nbe caught by `method_missing` (using fallthrough accessors), which is much slower.\n\nFor more details on dirty tracking, see the [API\ndocumentation](http://www.rubydoc.info/gems/mobility/Mobility/Plugins/Dirty).\n\n### Cache\n\nThe Mobility cache caches localized values that have been fetched once so they\ncan be quickly retrieved again. The cache plugin is included in the default\nconfiguration created by the install generator:\n\n```diff\n plugins do\n   # ...\n+  cache\n```\n\nIt can be disabled selectively per model by passing `cache: false` when\ndefining an attribute, like this:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  extend Mobility\n  translates :name, cache: false\nend\n```\n\nYou can also turn off the cache for a single fetch by passing `cache: false` to\nthe getter method, i.e. `post.title(cache: false)`. To remove the cache plugin\nentirely, remove the `cache` line from the global plugins configuration.\n\nThe cache is normally just a hash with locale keys and string (translation)\nvalues, but some backends (e.g. KeyValue and Table backends) have slightly more\ncomplex implementations.\n\n### \u003ca name=\"querying\"\u003e\u003c/a\u003eQuerying\n\nMobility backends also support querying on translated attributes. To enable\nthis feature, include the `query` plugin, and ensure you also have an ORM\nplugin enabled (`active_record` or `sequel`):\n\n```diff\n plugins do\n   # ...\n   active_record\n+  query\n```\n\nQuerying defines a scope or dataset class method, whose default name is `i18n`.\nYou can override this by passing a default in the configuration, like\n`query :t` to use a name `t`.\n\nQuerying is supported in two different ways. The first is via query methods\nlike `where` (and `not` and `find_by` in ActiveRecord, and `except` in Sequel).\n\nSo for ActiveRecord, assuming a model using KeyValue as its default backend:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  extend Mobility\n  translates :title,   type: :string\n  translates :content, type: :text\nend\n```\n\n... we can query for posts with title \"foo\" and content \"bar\" just as we would\nquery on untranslated attributes, and Mobility will convert the queries to\nwhatever the backend requires to actually return the correct results:\n\n```ruby\nPost.i18n.find_by(title: \"foo\", content: \"bar\")\n```\n\nresults in the SQL:\n\n```sql\nSELECT \"posts\".* FROM \"posts\"\nINNER JOIN \"mobility_string_translations\" \"Post_title_en_string_translations\"\n  ON \"Post_title_en_string_translations\".\"key\" = 'title'\n  AND \"Post_title_en_string_translations\".\"locale\" = 'en'\n  AND \"Post_title_en_string_translations\".\"translatable_type\" = 'Post'\n  AND \"Post_title_en_string_translations\".\"translatable_id\" = \"posts\".\"id\"\nINNER JOIN \"mobility_text_translations\" \"Post_content_en_text_translations\"\n  ON \"Post_content_en_text_translations\".\"key\" = 'content'\n  AND \"Post_content_en_text_translations\".\"locale\" = 'en'\n  AND \"Post_content_en_text_translations\".\"translatable_type\" = 'Post'\n  AND \"Post_content_en_text_translations\".\"translatable_id\" = \"posts\".\"id\"\nWHERE \"Post_title_en_string_translations\".\"value\" = 'foo'\n  AND \"Post_content_en_text_translations\".\"value\" = 'bar'\n```\n\nAs can be seen in the query above, behind the scenes Mobility joins two tables,\none with string translations and one with text translations, and aliases the\njoins for each attribute so as to match the particular model, attribute(s),\nlocale(s) and value(s) passed in to the query. Details of how this is done can\nbe found in the [Wiki page for the KeyValue\nbackend](https://github.com/shioyama/mobility/wiki/KeyValue-Backend#querying).\n\nYou can also use methods like `order`, `select`, `pluck` and `group` on\ntranslated attributes just as you would with normal attributes, and Mobility\nwill handle generating the appropriate SQL:\n\n```ruby\nPost.i18n.pluck(:title)\n#=\u003e [\"foo\", \"bar\", ...]\n```\n\nIf you would prefer to avoid the `i18n` scope everywhere, you can define it as\na default scope on your model:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  extend Mobility\n  translates :title,   type: :string\n  translates :content, type: :text\n  default_scope { i18n }\nend\n```\n\nNow translated attributes can be queried just like normal attributes:\n\n```ruby\nPost.find_by(title: \"Introducing Mobility\")\n#=\u003e finds post with English title \"Introducing Mobility\"\n```\n\nIf you want more fine-grained control over your queries, you can alternatively\npass a block to the query method and call attribute names from the block scope\nto build Arel predicates:\n\n```ruby\nPost.i18n do\n  title.matches(\"foo\").and(content.matches(\"bar\"))\nend\n```\n\nwhich generates the same SQL as above, except the `WHERE` clause becomes:\n\n```sql\nSELECT \"posts\".* FROM \"posts\"\n  ...\nWHERE \"Post_title_en_string_translations\".\"value\" ILIKE 'foo'\n  AND \"Post_content_en_text_translations\".\"value\" ILIKE 'bar'\n```\n\nThe block-format query format is very powerful and allows you to build complex\nbackend-independent queries on translated and untranslated attributes without\nhaving to deal with the details of how these translations are stored. The same\ninterface is supported with Sequel to build datasets.\n\n\u003ca name=\"backends\"\u003e\u003c/a\u003eBackends\n--------\n\nMobility supports different storage strategies, called \"backends\". The default\nbackend is the `KeyValue` backend, which stores translations in two tables, by\ndefault named `mobility_text_translations` and `mobility_string_translations`.\n\nYou can set the default backend to a different value in the global\nconfiguration, or you can set it explicitly when defining a translated\nattribute, like this:\n\n```ruby\nclass Word \u003c ApplicationRecord\n  translates :name, backend: :table\nend\n```\n\nThis would set the `name` attribute to use the `Table` backend (see below).\nThe `type` option (`type: :string` or `type: :text`) is missing here because\nthis is an option specific to the KeyValue backend (specifying which shared\ntable to store translations on). Backends have their own specific options; see\nthe [Wiki][wiki] and [API documentation][docs] for which options are available\nfor each.\n\nEverything else described above (fallbacks, dirty tracking, locale accessors,\ncaching, querying, etc) is the same regardless of which backend you use.\n\n### Table Backend (like Globalize)\n\nThe `Table` backend stores translations as columns on a model-specific table. If\nyour model uses the table `posts`, then by default this backend will store an\nattribute `title` on a table `post_translations`, and join the table to\nretrieve the translated value.\n\nTo use the table backend on a model, you will need to first create a\ntranslation table for the model, which (with Rails) you can do using the\n`mobility:translations` generator:\n\n```\nrails generate mobility:translations post title:string content:text\n```\n\nThis will generate the `post_translations` table with columns `title` and\n`content`, and all other necessary columns and indices. For more details see\nthe [Table\nBackend](https://github.com/shioyama/mobility/wiki/Table-Backend) page of the\nwiki and API documentation on the [`Mobility::Backend::Table`\nclass](http://www.rubydoc.info/gems/mobility/Mobility/Backends/Table).\n\n### Column Backend (like Traco)\n\nThe `Column` backend stores translations as columns with locale suffixes on\nthe model table. For an attribute `title`, these would be of the form\n`title_en`, `title_fr`, etc.\n\nUse the `mobility:translations` generator to add columns for locales in\n`I18n.available_locales` to your model:\n\n```\nrails generate mobility:translations post title:string content:text\n```\n\nFor more details, see the [Column\nBackend](https://github.com/shioyama/mobility/wiki/Column-Backend) page of the\nwiki and API documentation on the [`Mobility::Backend::Column`\nclass](http://www.rubydoc.info/gems/mobility/Mobility/Backends/Column).\n\n### PostgreSQL-specific Backends\n\nMobility also supports JSON and Hstore storage options, if you are using\nPostgreSQL as your database. To use this option, create column(s) on the model\ntable for each translated attribute, and set your backend to `:json`, `:jsonb`\nor `:hstore`. If you are using Sequel, note that you\nwill need to enable the [pg_json](http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_json_rb.html)\nor\n[pg_hstore](http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_hstore_rb.html)\nextensions with `DB.extension :pg_json` or `DB.extension :pg_hstore` (where\n`DB` is your database instance).\n\nAnother option is to store all your translations on a single jsonb column (one\nper model). This is called the \"container\" backend.\n\nFor details on these backends, see the [Postgres\nBackend](https://github.com/shioyama/mobility/wiki/Postgres-Backends-%28Column-Attribute%29)\nand [Container\nBackend](https://github.com/shioyama/mobility/wiki/Container-Backend)\npages of the wiki and in the API documentation\n([`Mobility::Backend::Jsonb`](http://www.rubydoc.info/gems/mobility/Mobility/Backends/Jsonb)\nand\n[`Mobility::Backend::Hstore`](http://www.rubydoc.info/gems/mobility/Mobility/Backends/Hstore)).\n\n*Note: The Json backend (`:json`) may also work with recent versions of MySQL\nwith JSON column support, although this backend/db combination is not tested.\nSee [this issue](https://github.com/shioyama/mobility/issues/226) for details.*\n\nDevelopment\n-----------\n\n### Custom Backends\n\nAlthough Mobility is primarily oriented toward storing ActiveRecord model\ntranslations, it can potentially be used to handle storing translations in\nother formats. In particular, the features mentioned above (locale accessors,\ncaching, fallbacks, dirty tracking to some degree) are not specific to database\nstorage.\n\nTo use a custom backend, simply pass the name of a class which includes\n`Mobility::Backend` to `translates`:\n\n```ruby\nclass MyBackend\n  include Mobility::Backend\n  # ...\nend\n\nclass MyClass\n  extend Mobility\n  translates :foo, backend: MyBackend\nend\n```\n\nFor details on how to define a backend class, see the [Introduction to Mobility\nBackends](https://github.com/shioyama/mobility/wiki/Introduction-to-Mobility-Backends)\npage of the wiki and the [API documentation on the `Mobility::Backend`\nmodule](http://www.rubydoc.info/gems/mobility/Mobility/Backend).\n\n### Testing Backends\n\nAll included backends are tested against a suite of shared specs which ensure\nthey conform to the same expected behaviour. These examples can be found in:\n\n- `spec/support/shared_examples/accessor_examples.rb` (minimal specs testing\n  translation setting/getting)\n- `spec/support/shared_examples/querying_examples.rb` (specs for\n  [querying](#querying))\n- `spec/support/shared_examples/serialization_examples.rb` (specialized specs\n  for backends which store translations as a Hash: `serialized`, `hstore`,\n  `json` and `jsonb` backends)\n\nA minimal test can simply define a model class and use helpers defined in\n`spec/support/helpers.rb` to run these examples, by extending either\n`Helpers::ActiveRecord` or `Helpers::Sequel`:\n\n```ruby\ndescribe MyBackend do\n  extend Helpers::ActiveRecord\n\n  before do\n    stub_const 'MyPost', Class.new(ActiveRecord::Base)\n    MyPost.extend Mobility\n    MyPost.translates :title, :content, backend: MyBackend\n  end\n\n  include_accessor_examples 'MyPost'\n  include_querying_examples 'MyPost'\n  # ...\nend\n```\n\nShared examples expect the model class to have translated attributes `title`\nand `content`, and an untranslated boolean column `published`. These defaults\ncan be changed, see the shared examples for details.\n\nBackends are also each tested against specialized specs targeted at their\nparticular implementations.\n\nIntegrations\n------------\n\n* [friendly_id-mobility](https://github.com/shioyama/friendly_id-mobility): Use\n  Mobility with [FriendlyId](https://github.com/norman/friendly_id).\n* [mobility-ransack](https://github.com/shioyama/mobility-ransack): Search\n  attributes translated by Mobility with\n  [Ransack](https://github.com/activerecord-hackery/ransack).\n* [mobility-actiontext](https://github.com/sedubois/mobility-actiontext): Translate\n  Rails [Action Text](https://guides.rubyonrails.org/action_text_overview.html) rich text\n  with Mobility.\n* [mobility_typed](https://github.com/GeorgeGorbanev/mobility_typed): Add type checking to Rails models accessors.\n\nTutorials\n---------\n\n- [Polyglot content in a rails\n  app](https://revs.runtime-revolution.com/polyglot-content-in-a-rails-app-aed823854955)\n- [Translating with\n  Mobility](https://dejimata.com/2017/3/3/translating-with-mobility)\n- [JSONify your Ruby\n  Translations](https://dejimata.com/2018/3/20/jsonify-your-ruby-translations)\n\nMore Information\n----------------\n\n- [Github repository](https://www.github.com/shioyama/mobility)\n- [API documentation][docs]\n- [Wiki][wiki]\n\n\u003ca name=\"#companies-using-mobility\"\u003e\u003c/a\u003eCompanies using Mobility\n------------------------\n\n\u003cimg alt=\"Logos of companies using Mobility\" src=\"./img/companies-using-mobility.png\" style=\"width: 100%\" /\u003e\n\n- [Doorkeeper](https://www.doorkeeper.jp/)\n- [Oreegano](https://www.oreegano.com/)\n- [Venuu](https://venuu.fi)\n- ... \u003csup\u003e\u0026#10033;\u003c/sup\u003e\n\n\u003csup\u003e\u0026#10033;\u003c/sup\u003e \u003csmall\u003ePost an issue or email me to add your company's name to this list.\u003c/small\u003e\n\nLicense\n-------\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshioyama%2Fmobility","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshioyama%2Fmobility","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshioyama%2Fmobility/lists"}