├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── app
└── controllers
│ └── translations_controller.rb
├── config
├── initializers
│ └── babbel.rb
└── routes.rb
├── inline_translation.gemspec
├── lib
├── generators
│ └── inline_translation
│ │ └── install
│ │ ├── install_generator.rb
│ │ └── templates
│ │ ├── add_inline_translations.rb
│ │ ├── create.js.erb
│ │ └── inline_translation.rb
├── inline_translation.rb
└── inline_translation
│ ├── concerns
│ ├── acts_as_translatable.rb
│ └── translatable.rb
│ ├── config
│ └── routes.rb
│ ├── engine.rb
│ ├── helpers
│ └── translations_helper.rb
│ ├── models
│ └── translation.rb
│ ├── services
│ └── translation_service.rb
│ ├── translators
│ ├── base.rb
│ ├── bing.rb
│ └── null.rb
│ └── version.rb
└── test
├── fixtures
├── application_controller.rb
└── rails.rb
├── inline_translation_integration_test.rb
├── lib
├── concerns
│ ├── acts_as_translatable_test.rb
│ └── translatable_test.rb
├── controllers
│ └── translations_controller_test.rb
├── generators
│ └── babbel_generator_install_test.rb
├── helpers
│ └── translations_helper_test.rb
├── models
│ └── translation_test.rb
├── services
│ └── translation_service_test.rb
└── translators
│ ├── base_test.rb
│ ├── bing_test.rb
│ └── null_test.rb
├── test_helper.rb
└── test_types
├── controller_test.rb
├── integration_test.rb
└── unit_test.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | *.bundle
11 | *.so
12 | *.o
13 | *.a
14 | mkmf.log
15 | *.gem
16 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in InlineTranslation.gemspec
4 | gemspec
5 |
6 | gem 'rails', '~> 4.1.0'
7 | gem 'bing_translator', '~> 4.4.0'
8 |
9 | group :development, :test do
10 | gem 'byebug', require: nil
11 | gem 'temping'
12 | gem 'mocha'
13 | gem 'sqlite3'
14 | end
15 |
16 | group :test do
17 | gem 'codeclimate-test-reporter', require: nil
18 | end
19 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 James Kiesel
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Inline Translation
2 |
3 | [](https://gitter.im/gdpelican/inline_translation?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |  [](https://codeclimate.com/github/gdpelican/inline_translation)
5 |
6 | `InlineTranslation` is a gem which provides your application with a simple, easy-to-use way to perform inline translations of content, into a variety of languages.
7 |
8 | It provides automatic caching (and cache-busting!) mechanisms to ensure you never have to request a translation twice, or serve up a stale translation.
9 |
10 | It's written as a wrapper for the fine [bing_translator gem](https://github.com/relrod/bing_translator-gem), but can be easily extended to using other translation services with a little elbow grease.
11 |
12 | ## Demo
13 |
14 | Check out the example app [here](http://inline-translation-test.herokuapp.com)
15 |
16 | ## Installation
17 |
18 | Add this line to your application's Gemfile:
19 |
20 | ```ruby
21 | gem 'inline_translation'
22 | ```
23 |
24 | And then execute:
25 |
26 | $ bundle
27 |
28 | Then, execute the install generator
29 |
30 | $ rails g inline_translation:install
31 |
32 | And migrate
33 |
34 | $ rake db:migrate
35 |
36 | Now you're all set up!
37 |
38 | ## Usage
39 |
40 | Inline Translation supplies several helper methods to make your translating life easier.
41 |
42 | To mark a field on an object as translatable, simply add
43 |
44 | `acts_as_translatable, on: :field_name`
45 |
46 | to your model.
47 |
48 | #### Additional options
49 |
50 | - **load_via** - the class method used to find a record for your model. Defaults to `:find`
51 | - **id_field** - field name for the unique identifier for your model. Defaults to `:id`
52 | - **language_field** - field name for the method / column name on your model to retrieve the language. Defaults to `language`
53 |
54 | NB: Oftentimes, you may wish to delegate this method to a user or other object, instead of storing the language on every model.
55 |
56 | For example:
57 |
58 | ```ruby
59 | # model.rb
60 | class Model < ActiveRecord::Base
61 | belongs_to :author, class_name: 'User'
62 | acts_as_translatable on: :column
63 |
64 | def language
65 | author.locale
66 | end
67 | end
68 | ```
69 |
70 | ## On the frontend
71 |
72 | #### The translation link
73 |
74 | InlineTranslation provides a simple helper method for translation links in the view.
75 |
76 | For example, adding
77 |
78 | ```ruby
79 | translate_link_for(@model, to: :fr)
80 | ```
81 |
82 | Will add an ajax link to create and store a French translation. The `to` field will default to I18n.locale.
83 |
84 | ###### Additional options
85 |
86 | - **text** - The text of the anchor generated. Defaults to 'Translate'
87 | - **to** - The language to translate to with this link. Defaults to I18n.locale
88 |
89 | (NB: This link will not appear if `@model.language` is equal to the 'to' parameter, since we cannot perform translations to the same language.)
90 |
91 | #### Populating the translation (via UJS)
92 |
93 | The simplest possible markup for including translations on callback:
94 |
95 | ```ruby
96 | # /app/views/models/show.html.erb
97 |
98 | <%= translated_element_for @model, :field_a %>
99 | <%= translated_element_for @model, :field_b %>
100 |
101 | ```
102 |
103 | (Note that this markup can occur anywhere, as long as the `translated_element_for` elements are within a div of the format 'className-id')
104 |
105 | If this particular markup structure doesn't work for you for whatever reason, feel free to edit the `app/views/translations/create.js.erb` with javascript to your liking.
106 |
107 | ###### Additional options
108 |
109 | - **element** - The type of element generated. Defaults to 'span'
110 |
111 | #### Populating the translation (via JSON)
112 |
113 | The `translations#create` action can also accept a `:json` format, which will return a list of serialized translations. These can be consumed by your javascript frontend framework as you see fit.
114 | (TODO: provide more robust support for custom serialization, such as through ActiveModel::Serializers PRs welcome!)
115 |
116 | ie, a simplistic implementation in angular:
117 |
118 | ```html
119 |
120 | Translate
121 | ```
122 |
123 | ```javascript
124 | // in the controller
125 | $scope.translateToFrench = function() {
126 | $http.post('/translations', { translatable_id: 1, translatable_type: 'Model', to: 'fr', format: 'json'}).then(function(data) {
127 | $scope.frenchTranslation = data
128 | })
129 | }
130 | ```
131 |
132 |
133 | ## On the backend
134 |
135 | InlineTranslation uses the Bing Translator API as a default. For instructions on setting up the Bing Translator API, [go here](https://github.com/relrod/bing_translator-gem#getting-a-client-id-and-secret).
136 |
137 | ## Different Translators
138 |
139 | Simply change the line in `config/initializers/inline_translation.rb` to use whatever translator you desire.
140 |
141 | Note that a custom translator must implement the following methods:
142 |
143 | - `self.ready?`: Returns true if the translator can translate anything
144 | - `can_translate?`: Returns true the translator can translate the given translatable
145 | - `translate`: Returns a translation for all `acts_as_translatable` fields on the translatable
146 |
147 | ## Contributing
148 |
149 | 1. Fork it ( https://github.com/gdpelican/inline_translation/fork )
150 | 2. Create your feature branch (`git checkout -b my-new-feature`)
151 | 3. Commit your changes (`git commit -am 'Add some feature'`)
152 | 4. Push to the branch (`git push origin my-new-feature`)
153 | 5. Create a new Pull Request
154 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rake/testtask'
3 |
4 | task default: :test
5 |
6 | Rake::TestTask.new :test do |t|
7 | t.libs << 'lib'
8 | t.libs << 'test'
9 | t.pattern = 'test/**/*_test.rb'
10 | t.verbose = true
11 | t.warning = false
12 | end
--------------------------------------------------------------------------------
/app/controllers/translations_controller.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Controllers
3 | class TranslationsController < ::ApplicationController
4 | respond_to :js, :json
5 |
6 | def create
7 | if service.translate(translatable, to: to_language)
8 | @translations = translatable.translations.where(language: to_language)
9 | respond_to do |format|
10 | format.js { render :create }
11 | format.json { render json: @translations } # TODO: support for AMS / custom serialization
12 | end
13 | else
14 | failure_response
15 | end
16 | end
17 |
18 | private
19 |
20 | def failure_response
21 | head :unprocessable_entity
22 | end
23 |
24 | def self.controller_path
25 | :translations
26 | end
27 |
28 | def service
29 | @service ||= InlineTranslation::Services::TranslationService.new
30 | end
31 |
32 | def translatable
33 | @translatable ||= params[:translatable_type].classify.constantize.get_instance params[:translatable_id] rescue nil
34 | end
35 |
36 | def to_language
37 | params[:to] || I18n.locale
38 | end
39 |
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/config/initializers/babbel.rb:
--------------------------------------------------------------------------------
1 | ActiveSupport.on_load(:active_record) { include InlineTranslation::Concerns::ActsAsTranslatable }
2 | ActiveSupport.on_load(:action_view) { include InlineTranslation::Helpers::TranslationsHelper }
3 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :translations, only: :create, module: 'inline_translation/controllers', as: :translations
3 | end
--------------------------------------------------------------------------------
/inline_translation.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'inline_translation/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "inline_translation"
8 | spec.version = InlineTranslation::VERSION
9 | spec.authors = ["James Kiesel (gdpelican)"]
10 | spec.email = ["james@loomio.org"]
11 |
12 | spec.summary = "Store on-the-fly translations using Bing (or others!)"
13 | spec.description = "Sets up a framework for allowing inline translation of database content"
14 | spec.homepage = "https://github.com/gdpelican/inline_translation"
15 | spec.license = "MIT"
16 |
17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18 | spec.bindir = "exe"
19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20 | spec.require_paths = ["app", "lib"]
21 |
22 | spec.add_runtime_dependency "rails", "~> 4.1"
23 | spec.add_runtime_dependency "bing_translator", "~> 4.4"
24 |
25 | spec.add_development_dependency "bundler", "~> 1.7"
26 | spec.add_development_dependency "rake", "~> 10.0"
27 | spec.add_development_dependency "minitest", "~> 5.7"
28 | end
29 |
--------------------------------------------------------------------------------
/lib/generators/inline_translation/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | class InstallGenerator < Rails::Generators::Base
3 | include Rails::Generators::Migration
4 | desc "Adds InlineTranslation translations table & initializer"
5 | source_root File.expand_path '../templates', __FILE__
6 |
7 | def copy_migration
8 | migration_template "add_inline_translations.rb", "db/migrate/add_inline_translations.rb"
9 | end
10 |
11 | def copy_initializer
12 | copy_file "inline_translation.rb", "config/initializers/inline_translation.rb"
13 | end
14 |
15 | def copy_js_view
16 | copy_file "create.js.erb", "app/views/translations/create.js.erb"
17 | end
18 |
19 | def self.next_migration_number(path)
20 | @previous_migration_nr ||= Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
21 | @previous_migration_nr += 1
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/generators/inline_translation/install/templates/add_inline_translations.rb:
--------------------------------------------------------------------------------
1 | class AddInlineTranslations < ActiveRecord::Migration
2 | def self.up
3 | create_table :translations do |t|
4 | t.integer :translatable_id
5 | t.string :translatable_type
6 | t.string :field
7 | t.string :language
8 | t.text :translation
9 | t.timestamps
10 | end
11 | end
12 |
13 | def self.down
14 | drop_table :translations
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/generators/inline_translation/install/templates/create.js.erb:
--------------------------------------------------------------------------------
1 | target = $('#<%= @translatable.class.to_s.downcase %>-<%= @translatable.id %>')
2 | <% @translations.each do |translation| %>
3 | target.find('.inline-translation-translated.<%= translation.field %>-translated').html("<%= j translation.translation %>")
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/lib/generators/inline_translation/install/templates/inline_translation.rb:
--------------------------------------------------------------------------------
1 | InlineTranslation.translator = InlineTranslation::Translators::Bing
2 |
--------------------------------------------------------------------------------
/lib/inline_translation.rb:
--------------------------------------------------------------------------------
1 | require 'active_support'
2 | require 'inline_translation/engine'
3 |
4 | module InlineTranslation
5 | extend ActiveSupport::Autoload
6 |
7 | cattr_accessor :translator
8 |
9 | module Concerns
10 | autoload :ActsAsTranslatable, 'inline_translation/concerns/acts_as_translatable'
11 | autoload :Translatable, 'inline_translation/concerns/translatable'
12 | end
13 |
14 | module Controllers
15 | autoload :TranslationsController, 'controllers/translations_controller'
16 | end
17 |
18 | module Generators
19 | autoload :InstallGenerator, 'generators/install/install_generator'
20 | end
21 |
22 | module Helpers
23 | autoload :TranslationsHelper, 'inline_translation/helpers/translations_helper'
24 | end
25 |
26 | module Models
27 | autoload :Translation, 'inline_translation/models/translation'
28 | end
29 |
30 | module Services
31 | autoload :TranslationService, 'inline_translation/services/translation_service'
32 | end
33 |
34 | module Translators
35 | autoload :Base, 'inline_translation/translators/base'
36 | autoload :Bing, 'inline_translation/translators/bing'
37 | autoload :Null, 'inline_translation/translators/null'
38 | end
39 |
40 | self.translator ||= Translators::Null
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/lib/inline_translation/concerns/acts_as_translatable.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Concerns
3 | module ActsAsTranslatable
4 | extend ActiveSupport::Concern
5 |
6 | module ClassMethods
7 | def acts_as_translatable(on: [], load_via: :find, id_field: :id, language_field: :language)
8 | include InlineTranslation::Concerns::Translatable
9 | define_singleton_method :translatable_fields, -> { Array on }
10 | define_singleton_method :get_instance, ->(id) { send load_via, id }
11 | define_method :id_field, -> { send id_field }
12 | define_method :language_field, -> { send language_field }
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/inline_translation/concerns/translatable.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Concerns
3 | module Translatable
4 | extend ActiveSupport::Concern
5 | included do
6 | has_many :translations, as: :translatable, class_name: 'InlineTranslation::Models::Translation'
7 | before_update :destroy_modified_translations
8 |
9 | private
10 |
11 | def destroy_modified_translations
12 | translations.each { |t| t.destroy if changed.include? t.field }
13 | end
14 | end
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/lib/inline_translation/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :translations, only: :create
3 | end
--------------------------------------------------------------------------------
/lib/inline_translation/engine.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | class Engine < ::Rails::Engine
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lib/inline_translation/helpers/translations_helper.rb:
--------------------------------------------------------------------------------
1 | require 'action_view/helpers'
2 |
3 | module InlineTranslation
4 | module Helpers
5 | module TranslationsHelper
6 | include ActionView::Helpers
7 |
8 | def translate_link_for(translatable, to: I18n.locale, text: "Translate")
9 | link_to text, path_for(translatable, to), method: :post, remote: true if translatable.language != to
10 | end
11 |
12 | def translated_element_for(translatable, field, element: :span, to: I18n.locale)
13 | content_tag element, '', class: "#{field}-translated to-#{to} inline-translation-translated"
14 | end
15 |
16 | private
17 |
18 | def path_for(translatable, to)
19 | translations_path translatable_id: translatable.id,
20 | translatable_type: translatable.class.to_s,
21 | to: to,
22 | action: :create
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/inline_translation/models/translation.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Models
3 | class Translation < ActiveRecord::Base
4 | belongs_to :translatable, polymorphic: true
5 | scope :to_language, ->(language) { where language: language }
6 | validates_presence_of :translatable, :language, :field, :translation
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/inline_translation/services/translation_service.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Services
3 | class TranslationService
4 | attr_reader :translator
5 |
6 | def initialize(translator_class = InlineTranslation.translator)
7 | raise InvalidTranslatorError.new unless translator_class.ready?
8 | @translator = translator_class.new
9 | end
10 |
11 | def translate(translatable, to: I18n.locale)
12 | translate!(translatable, to: to) rescue false
13 | end
14 |
15 | def translate!(translatable, to: I18n.locale)
16 | translatable.class.translatable_fields.map { |field| translate_field(translatable, field, to: to) }
17 | translatable.save
18 | end
19 |
20 | def translate_field(translatable, field, to: I18n.locale)
21 | translatable.translations.build(
22 | field: field,
23 | language: to,
24 | translation: @translator.translate(translatable.send(field), from: translatable.language_field, to: to)
25 | ) if @translator.can_translate?(translatable, field, to)
26 | end
27 |
28 | end
29 |
30 | class InvalidTranslatorError < StandardError
31 | def to_s
32 | "Unable to instantiate translator: Please ensure that the appropriate ENV variables are set"
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/inline_translation/translators/base.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Translators
3 | class Base
4 | attr_reader :translator
5 |
6 | def self.ready?
7 | false
8 | end
9 |
10 | def can_translate?(translatable, field, to)
11 | self.class.ready? &&
12 | to.present? &&
13 | translatable.respond_to?(field) &&
14 | translatable.language_field.present? &&
15 | translatable.language_field.to_s != to.to_s &&
16 | translatable.translations.where(field: field, language: to).empty?
17 | end
18 |
19 | def translate(text, from: nil, to: I18n.locale)
20 | raise NotImplementedError.new
21 | end
22 |
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/lib/inline_translation/translators/bing.rb:
--------------------------------------------------------------------------------
1 | require 'bing_translator'
2 |
3 | module InlineTranslation
4 | module Translators
5 | class Bing < Base
6 |
7 | def self.ready?
8 | ENV['BING_TRANSLATOR_APP_ID'] && ENV['BING_TRANSLATOR_SECRET'] && true
9 | end
10 |
11 | def initialize
12 | @translator = ::BingTranslator.new ENV['BING_TRANSLATOR_APP_ID'], ENV['BING_TRANSLATOR_SECRET']
13 | end
14 |
15 | def translate(text, from: nil, to: I18n.locale)
16 | @translator.translate text, from: from, to: to
17 | end
18 |
19 | end
20 | end
21 | end
--------------------------------------------------------------------------------
/lib/inline_translation/translators/null.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | module Translators
3 | class Null < Base
4 |
5 | def self.ready?
6 | true
7 | end
8 |
9 | def initialize
10 | end
11 |
12 | def translate(text, from: nil, to: I18n.locale)
13 | nil
14 | end
15 |
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/lib/inline_translation/version.rb:
--------------------------------------------------------------------------------
1 | module InlineTranslation
2 | VERSION = "0.1.1"
3 | end
4 |
--------------------------------------------------------------------------------
/test/fixtures/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | define_method :_routes, ->{}
3 | end
--------------------------------------------------------------------------------
/test/fixtures/rails.rb:
--------------------------------------------------------------------------------
1 | module Rails
2 | def self.application
3 | OpenStruct.new routes: routes,
4 | env_config: {}
5 | end
6 |
7 | def self.root
8 | Dir['../../../']
9 | end
10 |
11 | def self.env
12 | OpenStruct.new to_s: "test",
13 | development?: false,
14 | test?: true,
15 | production?: false
16 | end
17 |
18 | def self.backtrace_cleaner
19 | ActiveSupport::BacktraceCleaner.new
20 | end
21 |
22 | class Engine
23 | end
24 |
25 | private
26 |
27 | def self.routes
28 | @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
29 | routes.draw { resources :translations }
30 | end
31 | end
32 |
33 | def self.draw_routes
34 |
35 | end
36 | end
--------------------------------------------------------------------------------
/test/inline_translation_integration_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | class InlineTranslationIntegrationTest < IntegrationTest
3 | describe InlineTranslation do
4 | setup_model :integration_model
5 |
6 | let(:model) { IntegrationModel.create! column1: "column one", column2: "column2", language: :en }
7 |
8 | setup do
9 | @controller ||= InlineTranslation::Controllers::TranslationsController.new
10 | IntegrationModel.acts_as_translatable on: [:column1, :column2]
11 | InlineTranslation::Translators::Null.stubs(:ready?).returns(true)
12 | InlineTranslation::Translators::Null.any_instance.stubs(:translate).returns("this is a translation", "this is another translation")
13 | end
14 |
15 | describe "creating translations" do
16 | it "can create translations" do
17 | post :create, translatable_type: "IntegrationModel", translatable_id: model.id, to: :fr, format: :json
18 |
19 | created = Translation.where(translatable_type: "IntegrationModel")
20 |
21 | assert_equal created.where(translatable_id: model.id).size, 2
22 | assert_equal created.where(language: :fr).size, 2
23 | assert_equal created.where(translatable_type: "IntegrationModel").size, 2
24 | assert_equal created.where(translation: "this is a translation").size, 1
25 | assert_equal created.where(translation: "this is another translation").size, 1
26 |
27 |
28 | before_count = Translation.count
29 | post :create, translatable_type: "IntegrationModel", translatable_id: model.id, to: :fr, format: :json
30 | assert_equal Translation.count, before_count
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/test/lib/concerns/acts_as_translatable_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ActsAsTranslatableTest < UnitTest
4 | describe InlineTranslation::Concerns::ActsAsTranslatable do
5 |
6 | before do
7 | setup_model :concern_model
8 | ConcernModel.define_singleton_method(:find_alt) { |id| "found #{id}!" }
9 | end
10 |
11 | it "includes Translatable" do
12 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2]"
13 | assert ConcernModel.included_modules.include?(InlineTranslation::Concerns::Translatable)
14 | end
15 |
16 | it "defines a translatable_fields class method" do
17 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2]"
18 | assert_equal ConcernModel.translatable_fields, [:column1, :column2]
19 | end
20 |
21 | it "defines a single translatable_field correctly" do
22 | ConcernModel.class_eval "acts_as_translatable on: :column1"
23 | assert_equal ConcernModel.translatable_fields, [:column1]
24 | end
25 |
26 | it "defines a custom get_instance class method" do
27 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2], load_via: :find_alt"
28 | assert_equal ConcernModel.get_instance(42), ConcernModel.find_alt(42)
29 | end
30 |
31 | it "defines a get_instance class method as :find by default" do
32 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2]"
33 | model = ConcernModel.create
34 | assert_equal ConcernModel.get_instance(model.id), ConcernModel.find(model.id)
35 | end
36 |
37 | it "defines an id_field method as :id by default" do
38 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2]"
39 | model = ConcernModel.new id: 42
40 | assert_equal model.id_field, model.id
41 | end
42 |
43 | it "defines a custom id_field method" do
44 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2], id_field: :id_alt"
45 | model = ConcernModel.new id_alt: 42
46 | assert_equal model.id_field, model.id_alt
47 | end
48 |
49 | it "defines a language_field method as :language by default" do
50 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2]"
51 | model = ConcernModel.new language: :en
52 | assert_equal model.language_field, model.language
53 | end
54 |
55 | it "defines a custom language_field method" do
56 | ConcernModel.class_eval "acts_as_translatable on: [:column1, :column2], language_field: :language_alt"
57 | model = ConcernModel.new language_alt: :en
58 | assert_equal model.language_field, model.language_alt
59 | end
60 | end
61 |
62 | end
--------------------------------------------------------------------------------
/test/lib/concerns/translatable_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TranslatableTest < UnitTest
4 | describe InlineTranslation::Concerns::Translatable do
5 |
6 | let(:model) { ConcernModel.create column1: "test text" }
7 |
8 | before do
9 | setup_model :concern_model
10 | include_translatable ConcernModel
11 | end
12 |
13 | it "has_many translations" do
14 | assert_respond_to model, :translations
15 | assert_instance_of InlineTranslation::Models::Translation, model.translations.build
16 | end
17 |
18 | it "destroys translations after update" do
19 | model.translations.build language: :en, field: :column1, translation: "test translation"
20 | model.save
21 | assert_equal model.reload.translations.size, 1
22 |
23 | model.update! column1: "changed text"
24 | model.save
25 |
26 | assert_equal model.reload.translations.size, 0
27 | end
28 |
29 | end
30 |
31 | end
--------------------------------------------------------------------------------
/test/lib/controllers/translations_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TranslationsControllerTest < ControllerTest
4 | describe InlineTranslation::Controllers::TranslationsController do
5 | setup_model :controller_model
6 | let(:service) { InlineTranslation::Services::TranslationService.new }
7 | let(:translatable) { ControllerModel.create }
8 | let(:translation_result) { { column1: 'A translation!', column2: 'A translation!' } }
9 |
10 | setup do
11 | ControllerModel.class_eval "acts_as_translatable on: [:column1, :column2]"
12 | @controller ||= InlineTranslation::Controllers::TranslationsController.new
13 | translatable
14 | end
15 |
16 | describe "POST create" do
17 | it "returns the translation for successful translation for JSON" do
18 | InlineTranslation.stubs(:ready?).returns(true)
19 | InlineTranslation::Translators::Null.any_instance.stubs(:can_translate?).returns(true)
20 | InlineTranslation::Translators::Null.any_instance.stubs(:translate).returns("A translation!")
21 | post :create, translatable_type: "ControllerModel", translatable_id: translatable.id, format: :json
22 |
23 | assert_equal response.status, 200
24 | json = JSON.parse(response.body)
25 |
26 | assert_equal json.length, translatable.translations.size
27 | fields = json.map { |t| t['field'] }
28 | translatable_ids = json.map { |t| t['translatable_id'] }
29 | translatable_types = json.map { |t| t['translatable_type'] }
30 |
31 | assert_includes fields, 'column1'
32 | assert_includes fields, 'column2'
33 | assert_includes translatable_ids, translatable.id
34 | assert_includes translatable_types, 'ControllerModel'
35 | end
36 |
37 | it "returns the translation for successful translation for JS" do
38 | # TODO: stub out the call to render, which errors because we don't have a translations#create view
39 | skip "Stub out render call so there's no 'cannot find translations#create view' error"
40 | InlineTranslation::Translators::Null.any_instance.stubs(:can_translate?).returns(true)
41 | InlineTranslation::Translators::Null.any_instance.stubs(:translate).returns("A translation!")
42 | ActionView::Renderer.any_instance.stubs(:render).with('translations/create').returns('wark')
43 | post :create, translatable_type: "ControllerModel", translatable_id: translatable.id, format: :js
44 |
45 | assert_equal response.status, 200
46 | end
47 |
48 |
49 | it "returns unprocessable entity for unsuccessful translation" do
50 | InlineTranslation::Services::TranslationService.any_instance.stubs(:translate).returns(false)
51 | post :create, translatable_type: "ControllerModel", translatable_id: translatable.id, format: :json
52 | assert_equal response.status, 422
53 | end
54 |
55 | it "returns unprocessable entity when translatable_type is not defined" do
56 | InlineTranslation::Services::TranslationService.any_instance.stubs(:translate).returns(false)
57 | post :create, format: :json
58 | assert_equal response.status, 422
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/test/lib/generators/babbel_generator_install_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'generators/inline_translation/install/install_generator'
3 |
4 | class InlineTranslationGeneratorInstallTest < Rails::Generators::TestCase
5 | tests InlineTranslation::InstallGenerator
6 | setup_destination
7 |
8 | test "generates a migration file" do
9 | run_generator
10 | assert_migration "db/migrate/add_inline_translations.rb"
11 | end
12 |
13 | test "generates an initializer file" do
14 | run_generator
15 | assert_file "config/initializers/inline_translation.rb"
16 | end
17 |
18 | test "generates a create js view file" do
19 | run_generator
20 | assert_file "app/views/translations/create.js.erb"
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/lib/helpers/translations_helper_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TranslationsHelperTest < UnitTest
4 | include InlineTranslation::Helpers::TranslationsHelper
5 | include Rails.application.routes.url_helpers
6 |
7 | setup_model :helper_model
8 |
9 | let(:model) { HelperModel.new column1: 'a value', language: :en }
10 |
11 | before do
12 | I18n.locale = :fr
13 | end
14 |
15 | describe ".translate_link_for" do
16 | it "returns a link with another locale set" do
17 | assert_match /\?to=fr/, translate_link_for(model)
18 | end
19 |
20 | it "returns a link with a specified locale" do
21 | I18n.locale = :en
22 | assert_match /\?to=fr/, translate_link_for(model, to: :fr)
23 | end
24 |
25 | it "defaults to 'Translate' for link text" do
26 | assert_match /Translate/, translate_link_for(model)
27 | end
28 |
29 | it "accepts a text parameter" do
30 | assert_match /Other text/, translate_link_for(model, text: 'Other text')
31 | end
32 |
33 | it "does not return a link when locale is the same" do
34 | I18n.locale = :en
35 | refute translate_link_for(model)
36 | end
37 |
38 | it "does not return a link when specified locale is the same" do
39 | refute translate_link_for(model, to: :en)
40 | end
41 | end
42 |
43 | describe ".translated_element_for" do
44 | it "returns an span with a language class" do
45 | assert_match /to-fr/, translated_element_for(model, :column1)
46 | end
47 |
48 | it "returns a span with a field class" do
49 | assert_match /column1-translated/, translated_element_for(model, :column1)
50 | end
51 |
52 | it "returns a span with a InlineTranslation class" do
53 | assert_match /inline-translation-translated/, translated_element_for(model, :column1)
54 | end
55 |
56 | it "accepts an element parameter" do
57 | assert_match /{ InlineTranslation::Services::TranslationService.new(translator_class) }.must_raise InlineTranslation::Services::InvalidTranslatorError
28 | end
29 | end
30 |
31 | describe "invalid translator error" do
32 | it "has an error message" do
33 | assert_match /Unable to instantiate translator/, InlineTranslation::Services::InvalidTranslatorError.new.to_s
34 | end
35 | end
36 |
37 | describe "translate" do
38 | it "returns the results of translate!" do
39 | service.stubs(:translate!).returns("translation result")
40 | assert_equal service.translate(translatable), "translation result"
41 | end
42 | end
43 |
44 | describe "translate!" do
45 | it "builds all translations using translate_field" do
46 | service.translate!(translatable)
47 | assert_equal translatable.translations.size, 2
48 | end
49 |
50 | it "does not build an invalid translation" do
51 | service.translator.stubs(:can_translate?).returns(false)
52 | service.translate!(translatable)
53 | assert_equal translatable.translations.size, 0
54 | end
55 | end
56 |
57 | describe "translate_field" do
58 | it "builds a translation with translate_field" do
59 | service.translate_field(translatable, :column1)
60 | assert_equal translatable.translations.size, 1
61 | end
62 | it "does not build an invalid translation" do
63 | service.translator.stubs(:can_translate?).returns(false)
64 | service.translate_field(translatable, :column1)
65 | assert_equal translatable.translations.size, 0
66 | end
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/test/lib/translators/base_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'inline_translation/translators/base'
3 |
4 | class BaseTest < UnitTest
5 | describe InlineTranslation::Translators::Base do
6 | setup_model :translator_model
7 |
8 | let(:translator_class) { InlineTranslation::Translators::Base }
9 | let(:translator) { translator_class.new }
10 | let(:translatable) { TranslatorModel.new column1: "column one", language: :en }
11 |
12 | setup do
13 | TranslatorModel.acts_as_translatable on: :column1
14 | end
15 |
16 | it "returns false on ready?" do
17 | refute translator_class.ready?
18 | end
19 |
20 | describe ".can_translate?" do
21 |
22 | it "returns false if the translator is not ready" do
23 | refute translator.can_translate? translatable, :column1, :fr
24 | end
25 |
26 | it "returns false if 'to' is not set" do
27 | translator.stubs(:ready?).returns(:true)
28 | refute translator.can_translate? translatable, :column1, nil
29 | end
30 |
31 | it "returns false if translatable's language is not set" do
32 | translator.stubs(:ready?).returns(:true)
33 | translatable.language = nil
34 | refute translator.can_translate? translatable, :column1, :fr
35 | end
36 |
37 | it "returns false if the from and to language are the same" do
38 | translator.stubs(:ready?).returns(:true)
39 | refute translator.can_translate? translatable, :column1, :en
40 | end
41 |
42 | it "returns false if a translation already exists" do
43 | translator.stubs(:ready?).returns(:true)
44 | translatable.save
45 | translatable.translations.create field: :column1, language: :fr
46 | refute translator.can_translate? translatable, :column1, :fr
47 | end
48 |
49 | it "returns false if the field is not found on translatable" do
50 | translator_class.stubs(:ready?).returns(:true)
51 | refute translator.can_translate? translatable, :notacolumn, :fr
52 | end
53 |
54 | it "returns true otherwise" do
55 | translator_class.stubs(:ready?).returns(:true)
56 | assert translator.can_translate? translatable, :column1, :fr
57 | end
58 | end
59 |
60 | it "raises an error on translate" do
61 | assert_raises NotImplementedError do translator.translate(nil) end
62 | end
63 |
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/test/lib/translators/bing_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'inline_translation/translators/base'
3 | require 'inline_translation/translators/bing'
4 | require 'bing_translator'
5 |
6 | class BingTest < UnitTest
7 | describe InlineTranslation::Translators::Bing do
8 |
9 | let(:translator_class) { InlineTranslation:: Translators::Bing }
10 | let(:translator) { translator_class.new }
11 |
12 | before do
13 | setup_bing_translator_env
14 | end
15 |
16 | it "initializes a BingTranslator" do
17 | assert_instance_of BingTranslator, translator.translator
18 | end
19 |
20 | it "returns ready if ENV variables are set" do
21 | assert translator_class.ready?
22 | end
23 |
24 | it "returns not ready if app id is not set" do
25 | ENV['BING_TRANSLATOR_APP_ID'] = nil
26 | refute translator_class.ready?
27 | end
28 |
29 | it "returns not ready if secret is not set" do
30 | ENV['BING_TRANSLATOR_SECRET'] = nil
31 | refute translator_class.ready?
32 | end
33 |
34 | it "can translate a translatable" do
35 | translator.translator.stubs(:translate).returns("translation")
36 | assert_equal translator.translate("original"), "translation"
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/test/lib/translators/null_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'inline_translation/translators/base'
3 | require 'inline_translation/translators/null'
4 |
5 | class NullTest < UnitTest
6 | describe InlineTranslation::Translators::Null do
7 |
8 | let(:translator_class) { InlineTranslation::Translators::Null }
9 | let(:translator) { translator_class.new }
10 |
11 | it "returns ready as true" do
12 | assert translator_class.ready?
13 | end
14 |
15 | it "returns nil as a translation" do
16 | assert_nil translator.translate("anything")
17 | end
18 |
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] = 'test'
2 |
3 | require "codeclimate-test-reporter"
4 | CodeClimate::TestReporter.start
5 |
6 | require 'byebug'
7 | require 'bundler/setup'
8 | require 'active_record'
9 | require 'temping'
10 | ActiveRecord::Base.establish_connection adapter: :sqlite3, database: ':memory:'
11 |
12 | require 'action_controller'
13 |
14 | require 'fixtures/rails'
15 | require 'fixtures/application_controller'
16 |
17 | require 'minitest/autorun'
18 | require 'minitest/pride'
19 | require 'mocha/mini_test'
20 | require 'rails/test_help'
21 |
22 | require 'test_types/unit_test'
23 | require 'test_types/controller_test'
24 | require 'test_types/integration_test'
25 |
26 | require 'inline_translation'
27 |
28 | I18n.enforce_available_locales = false
29 |
30 | def setup_destination
31 | destination File.expand_path '../../../tmp', __FILE__
32 | setup :prepare_destination
33 | end
34 |
35 | def setup_model(model = :test_model)
36 | constantized = model.to_s.split("_").collect(&:capitalize).join
37 | unless Object.const_defined?(constantized)
38 | Temping.create model do
39 | with_columns do |t|
40 | t.integer :id_alt
41 | t.string :column1, :column2, :language, :language_alt
42 | end
43 | end
44 | include_acts_as_translatable Object.const_get(constantized)
45 | end
46 | end
47 |
48 | def setup_translation
49 | unless Object.const_defined?("Translation")
50 | Temping.create :translation do
51 | with_columns do |t|
52 | t.integer :translatable_id
53 | t.string :translatable_type
54 | t.string :field
55 | t.string :language
56 | t.text :translation
57 | t.timestamps
58 | end
59 | end
60 | end
61 | end
62 |
63 | def setup_bing_translator_env
64 | ENV['BING_TRANSLATOR_APP_ID'] = 'set'
65 | ENV['BING_TRANSLATOR_SECRET'] = 'set'
66 | end
67 |
68 | def include_acts_as_translatable(model)
69 | model.class_eval "include InlineTranslation::Concerns::ActsAsTranslatable"
70 | end
71 |
72 | def include_translatable(model)
73 | model.class_eval "include InlineTranslation::Concerns::Translatable"
74 | end
75 |
--------------------------------------------------------------------------------
/test/test_types/controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'action_controller'
2 |
3 | class ControllerTest < UnitTest
4 | include ActionController::TestCase::Behavior
5 | before { @routes = Rails.application.routes }
6 | end
--------------------------------------------------------------------------------
/test/test_types/integration_test.rb:
--------------------------------------------------------------------------------
1 | class IntegrationTest < ControllerTest
2 | end
--------------------------------------------------------------------------------
/test/test_types/unit_test.rb:
--------------------------------------------------------------------------------
1 | class UnitTest < MiniTest::Spec
2 | include ActiveSupport::Testing::SetupAndTeardown
3 | end
--------------------------------------------------------------------------------