├── test ├── dummy │ ├── log │ │ └── .gitkeep │ ├── lib │ │ └── assets │ │ │ └── .gitkeep │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── helpers │ │ │ ├── posts_helper.rb │ │ │ └── application_helper.rb │ │ ├── controllers │ │ │ ├── test_controller.rb │ │ │ └── application_controller.rb │ │ ├── views │ │ │ ├── test │ │ │ │ └── test.html.erb │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ └── assets │ │ │ ├── stylesheets │ │ │ └── application.css.erb │ │ │ └── javascripts │ │ │ └── application.js.erb │ ├── config │ │ ├── routes.rb │ │ ├── environment.rb │ │ ├── initializers │ │ │ ├── mime_types.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── session_store.rb │ │ │ ├── wrap_parameters.rb │ │ │ └── inflections.rb │ │ ├── boot.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── secrets.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ │ └── application.rb │ ├── config.ru │ ├── Rakefile │ ├── script │ │ └── rails │ └── README.rdoc ├── test_helper.rb └── i18n_viz_test.rb ├── .rvmrc.example ├── lib ├── i18n_viz │ ├── version.rb │ ├── engine.rb │ ├── view_helpers.rb │ └── middleware.rb ├── i18n_viz.rb └── generators │ └── i18n_viz │ └── install_generator.rb ├── .gitignore ├── .travis.yml ├── .evergreen ├── sass └── i18n_viz.css.sass ├── i18n_viz.gemspec ├── config.rb ├── Gemfile ├── MIT-LICENSE ├── assets ├── stylesheets │ └── i18n_viz.css └── javascripts │ └── i18n_viz.js ├── CHANGELOG.md ├── Rakefile ├── spec └── javascripts │ └── i18n_viz_spec.coffee └── README.md /test/dummy/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rvmrc.example: -------------------------------------------------------------------------------- 1 | rvm 1.9.3-perf@i18n_viz --create 2 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /lib/i18n_viz/version.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | VERSION = "1.2.0" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | get '/test' => 'test#test' 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/test_controller.rb: -------------------------------------------------------------------------------- 1 | class TestController < ApplicationController 2 | def test 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/log/*.log 6 | test/dummy/tmp/ 7 | test/dummy/.sass-cache 8 | Gemfile.lock 9 | *.sublime-* 10 | .rvmrc 11 | .sass-cache 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | script: rake test spec:javascripts 4 | rvm: 5 | - 2.2.4 6 | - 2.3.0 7 | before_script: 8 | - "export DISPLAY=:99.0" 9 | - "sh -e /etc/init.d/xvfb start" 10 | -------------------------------------------------------------------------------- /.evergreen: -------------------------------------------------------------------------------- 1 | require 'capybara-webkit' 2 | 3 | Evergreen.configure do |config| 4 | #config.driver = :webkit 5 | config.public_dir = 'assets' 6 | #config.template_dir = 'templates' 7 | config.spec_dir = 'spec' 8 | end 9 | -------------------------------------------------------------------------------- /lib/i18n_viz/engine.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | class Engine < ::Rails::Engine 3 | initializer "i18n_viz.view_helpers" do 4 | ActiveSupport.on_load(:action_view) do 5 | include I18nViz::ViewHelpers 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /lib/i18n_viz.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | mattr_accessor :external_tool_url, :enabled 3 | 4 | @@enabled = true 5 | 6 | def self.enabled? 7 | @@enabled 8 | end 9 | end 10 | 11 | require 'i18n_viz/view_helpers' 12 | require 'i18n_viz/engine' 13 | require 'i18n_viz/middleware' 14 | 15 | -------------------------------------------------------------------------------- /test/dummy/app/views/test/test.html.erb: -------------------------------------------------------------------------------- 1 | <%= t("hello") %> 2 | <%= translate("foo", :i18n_viz => false) %> 3 | <%= translate("bar", :scope => :scope) %> 4 |

5 | <%= t("hello") %> 6 | <%= translate("bar", :scope => :scope) %> 7 |

8 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | if File.exist?(gemfile) 5 | ENV['BUNDLE_GEMFILE'] = gemfile 6 | require 'bundler' 7 | Bundler.setup 8 | end 9 | 10 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | foo: "bar" 7 | scope: 8 | bar: "foo" 9 | -------------------------------------------------------------------------------- /test/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application", :media => "all" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | development: 2 | secret_key_base: 'f663201ec4caeb717b93ac5f49cbf722d9026f134688c6c7452abf773db92a75067eb4df824124115ecc13f041a43be432590b8aa193ca580f755e4a25ea28a8' 3 | 4 | test: 5 | secret_key_base: 'f663201ec4caeb717b93ac5f49cbf722d9026f134688c6c7452abf773db92a75067eb4df824124115ecc13f041a43be432590b8aa193ca580f755e4a25ea28a8' 6 | 7 | production: 8 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css.erb: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js.erb: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | -------------------------------------------------------------------------------- /sass/i18n_viz.css.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3/border-radius 2 | @import compass/css3/images 3 | 4 | #i18n_viz_tooltip 5 | display: none 6 | position: absolute 7 | top: 100px 8 | left: 200px 9 | border: 2px solid #A9A9A9 10 | +border-radius(5px) 11 | background-color: #F5F5F5 12 | +background-image(linear-gradient(#FFFFFF, #F5F5F5)) 13 | padding: 10px 14 | z-index: 999 15 | a, span 16 | margin-right: 10px 17 | color: #111 18 | font-family: "Helvetica Neue", Arial, sans-serif 19 | font-size: 12px 20 | &:last-child 21 | margin-right: 0 22 | 23 | .i18n-viz 24 | cursor: help 25 | background: lightyellow !important 26 | &:hover 27 | color: black 28 | background: yellow !important 29 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 5 | require "rails/test_help" 6 | 7 | Rails.backtrace_cleaner.remove_silencers! 8 | 9 | # Load support files 10 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 11 | 12 | 13 | 14 | 15 | # ---- capybara config ---- 16 | 17 | require "capybara/rails" 18 | #require "capybara/webkit" 19 | 20 | class ActionDispatch::IntegrationTest 21 | # Make the Capybara DSL available in all integration tests 22 | include Capybara::DSL 23 | 24 | Capybara.javascript_driver = :selenium 25 | 26 | teardown do 27 | Capybara.reset_sessions! # Forget the (simulated) browser state 28 | Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/generators/i18n_viz/install_generator.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | module Generators 3 | class InstallGenerator < Rails::Generators::Base 4 | source_root File.expand_path("../../../../app/assets/", __FILE__) 5 | 6 | def copy_assets 7 | copy_file "javascripts/i18n_viz.js", "public/javascripts/i18n_viz.js" 8 | copy_file "stylesheets/i18n_viz.css", "public/stylesheets/i18n_viz.css" 9 | end 10 | 11 | def create_initializer 12 | initializer "i18n_viz.rb", %Q{ 13 | I18nViz.enabled = !Rails.env.production? 14 | I18nViz.external_tool_url = "" # link to more information about each i18n key, e.g. "http://mytranslationtool.com/?key=" -- the key (e.g. 'en.foo.bar') will be appended to this URL 15 | I18nViz.css_override = "" # CSS override text 16 | } 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /i18n_viz.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "i18n_viz/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "i18n_viz" 9 | s.version = I18nViz::VERSION 10 | s.authors = ["Jakob Hilden"] 11 | s.email = ["jakobhilden@gmail.com"] 12 | s.homepage = "https://github.com/jhilden/i18n_viz" 13 | s.summary = "Visualize i18n strings alongside their keys within the frontend of internationalized Rails/Ruby web applications." 14 | s.description = "I18nViz will help everyone to figure out where which i18n key is being used by adding a little overlay which can even link to any existing translation platform." 15 | 16 | s.files = Dir["{assets,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] 17 | s.test_files = Dir["test/**/*"] 18 | end 19 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "app/assets/stylesheets" 6 | sass_dir = "sass" 7 | images_dir = "images" 8 | javascripts_dir = "javascripts" 9 | 10 | # You can select your preferred output style here (can be overridden via the command line): 11 | # output_style = :expanded or :nested or :compact or :compressed 12 | 13 | # To enable relative paths to assets via compass helper functions. Uncomment: 14 | # relative_assets = true 15 | 16 | # To disable debugging comments that display the original location of your selectors. Uncomment: 17 | # line_comments = false 18 | 19 | 20 | # If you prefer the indented syntax, you might want to regenerate this 21 | # project again passing --syntax sass, or you can uncomment this: 22 | # preferred_syntax = :sass 23 | # and then run: 24 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 25 | preferred_syntax = :sass -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Declare your gem's dependencies in i18n_viz.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | group :test do 9 | gem "rake" 10 | gem "tzinfo" 11 | 12 | # used by the dummy application 13 | gem "rails", "4.2.7" 14 | gem "jquery-rails" 15 | gem "coffee-script" 16 | 17 | # testing libraries 18 | gem "capybara" 19 | gem "evergreen" 20 | gem "capybara-webkit" 21 | gem 'selenium-webdriver' 22 | end 23 | 24 | group :assets do 25 | gem 'therubyracer' 26 | gem 'coffee-rails' 27 | gem 'uglifier' 28 | gem 'sprockets-rails' 29 | end 30 | 31 | group :development do 32 | gem "launchy" # debugging 33 | gem "compass" 34 | gem "pry" 35 | end 36 | 37 | # Declare any dependencies that are still in development here instead of in 38 | # your gemspec. These might include edge Rails or gems from your path or 39 | # Git. Remember to move these dependencies to your gemspec before releasing 40 | # your gem to rubygems.org. 41 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Jakob Hilden 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/i18n_viz/view_helpers.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | module ViewHelpers 3 | def translate(key, options = {}) # TODO: alias 4 | if display_i18n_viz? && options[:i18n_viz] != false 5 | # TODO: ActionController::Base.perform_caching = false if ActionController::Base.perform_caching == true 6 | if !options[:scope].blank? 7 | "#{super(key, options)}--#{options[:scope]}.#{scope_key_by_partial(key)}--" 8 | else 9 | "#{super(key, options)}--#{scope_key_by_partial(key)}--" 10 | end 11 | else 12 | super(key, options) 13 | end 14 | end 15 | alias t translate 16 | 17 | def i18n_viz_include_tag # TODO: doesn't work yet 18 | return unless display_i18n_viz? 19 | 20 | stylesheet_link_tag 'i18n_viz' 21 | javascript_include_tag 'i18n_viz' 22 | end 23 | 24 | def display_i18n_viz? 25 | check_params 26 | rescue 27 | false # rescue workaround, because params is weirdly defined in e.g. ActionMailer 28 | end 29 | 30 | private 31 | 32 | def check_params 33 | return true if params && params[:i18n_viz] 34 | return true if cookies && cookies[:i18n_viz] 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /assets/stylesheets/i18n_viz.css: -------------------------------------------------------------------------------- 1 | #i18n_viz_tooltip { 2 | display: none; 3 | position: absolute; 4 | top: 100px; 5 | left: 200px; 6 | border: 2px solid darkgrey; 7 | -webkit-border-radius: 5px; 8 | -moz-border-radius: 5px; 9 | -ms-border-radius: 5px; 10 | -o-border-radius: 5px; 11 | border-radius: 5px; 12 | background-color: whitesmoke; 13 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5)); 14 | background-image: -webkit-linear-gradient(#ffffff, #f5f5f5); 15 | background-image: -moz-linear-gradient(#ffffff, #f5f5f5); 16 | background-image: -o-linear-gradient(#ffffff, #f5f5f5); 17 | background-image: linear-gradient(#ffffff, #f5f5f5); 18 | padding: 10px; 19 | z-index: 999; 20 | } 21 | #i18n_viz_tooltip a, #i18n_viz_tooltip span { 22 | margin-right: 10px; 23 | color: #111111; 24 | font-family: "Helvetica Neue", Arial, sans-serif; 25 | font-size: 12px; 26 | } 27 | #i18n_viz_tooltip a:last-child, #i18n_viz_tooltip span:last-child { 28 | margin-right: 0; 29 | } 30 | 31 | .i18n-viz { 32 | cursor: help; 33 | background: lightyellow !important; 34 | } 35 | 36 | .i18n-viz:hover { 37 | color: black; 38 | background: yellow !important; 39 | } 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2.0 2 | 3 | * [improvement] using the i18n_viz cookie, you can switch i18n_viz on in a 4 | more permanent fashion. 5 | 6 | ## 1.1.0 7 | 8 | * [improvement] added the ability to use a `i18n_viz` cookie 9 | * [improvement] added `I18nViz.css_override` setting 10 | 11 | ## 1.0.0 12 | 13 | * change of plans :) no, really the complete installation has changed, this is 14 | now a middleware which takes care of all the css and js injection and only does 15 | this when `i18n_viz` is in the query string. 16 | 17 | * [bugfix] keep nested HTML tags when removing i18n keys. Fixes #9 18 | 19 | ## 0.4.0 20 | 21 | * [bugfix] tooltip displays correct key for translations that are using :scope option 22 | * [bugfix] catch error when contents method raises exception 23 | * [improvement] update test app routes to work with rails 4 24 | 25 | ## 0.3.2 26 | 27 | * [bugfix] i18n key meta information wasn't cleared correctly from element's text 28 | * [bugfix] problem with UTF-8 encoded `I18nViz.external_tool_url`, thanks to @eduoard 29 | * [improvement] CSS tooltip text legibility, @eduoard 30 | 31 | ## 0.3.1 32 | 33 | minor bugfixes in the JavaScrit and CSS 34 | 35 | ## 0.3.0 36 | 37 | * The gem is now a Rails Engine and is targeted to work mostly with Rails versions 3.1+ 38 | * Switched to CoffeeScript 39 | * Renamed `I18nViz.key_url` to `I18nViz.external_tool_url` 40 | 41 | ## 0.0.2 42 | 43 | minor bugfixes 44 | 45 | ## 0.0.1 46 | 47 | initial Version 48 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | begin 8 | require 'rdoc/task' 9 | rescue LoadError 10 | require 'rdoc/rdoc' 11 | require 'rake/rdoctask' 12 | RDoc::Task = Rake::RDocTask 13 | end 14 | 15 | RDoc::Task.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'I18nViz' 18 | rdoc.options << '--line-numbers' 19 | rdoc.rdoc_files.include('README.rdoc') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | 23 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) 24 | load 'rails/tasks/engine.rake' 25 | 26 | 27 | Bundler::GemHelper.install_tasks 28 | 29 | require 'rake/testtask' 30 | require 'evergreen' 31 | 32 | namespace :spec do 33 | desc "Run JavaScript specs via Evergreen" 34 | task :javascripts => :environment do 35 | Evergreen.root = File.expand_path('.') 36 | result = Evergreen::Runner.new.run 37 | Kernel.exit(1) unless result 38 | end 39 | end 40 | 41 | Rake::TestTask.new(:test) do |t| 42 | t.libs << 'lib' 43 | t.libs << 'test' 44 | t.pattern = 'test/**/*_test.rb' 45 | t.verbose = false 46 | 47 | #Rake::Task[:js_tests].invoke 48 | end 49 | 50 | task :travis do 51 | ["rake test"].each do |cmd| 52 | puts "Starting to run #{cmd}..." 53 | system("export DISPLAY=:99.0 && bundle exec #{cmd}") 54 | raise "#{cmd} failed!" unless $?.exitstatus == 0 55 | end 56 | end 57 | 58 | task :default => :test 59 | -------------------------------------------------------------------------------- /lib/i18n_viz/middleware.rb: -------------------------------------------------------------------------------- 1 | module I18nViz 2 | class Middleware 3 | JS = File.read(File.join(File.dirname(__FILE__), '..', '..', 'assets', 'javascripts', 'i18n_viz.js' )) 4 | CSS = File.read(File.join(File.dirname(__FILE__), '..', '..', 'assets', 'stylesheets', 'i18n_viz.css' )) 5 | 6 | attr_accessor :external_tool_url 7 | attr_accessor :css_override 8 | def initialize(app, &block) 9 | @app = app 10 | 11 | yield(self) if block_given? 12 | end 13 | 14 | def call(env) 15 | @status, @headers, @body = @app.call(env) 16 | return [@status, @headers, @body] if !html? || !(env["QUERY_STRING"] =~ /i18n_viz/ || env["HTTP_COOKIE"] =~ /i18n_viz/) 17 | 18 | response = Rack::Response.new([], @status, @headers) 19 | 20 | @body.each { |fragment| response.write inject(env, fragment) } 21 | @body.close if @body.respond_to?(:close) 22 | 23 | response.finish 24 | end 25 | 26 | private 27 | 28 | def html?; @headers['Content-Type'] =~ /html/; end 29 | 30 | def inject(env, response) 31 | response.sub! %r{} do |m| 32 | style_and_script = %Q{ 33 | 41 | 42 | 46 | } 47 | style_and_script << m.to_s 48 | end 49 | response 50 | end 51 | end 52 | 53 | end 54 | 55 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | #config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Raise exception on mass assignment protection for Active Record models 26 | #config.active_record.mass_assignment_sanitizer = :strict 27 | 28 | # Log the query plan for queries taking more than this (works 29 | # with SQLite, MySQL, and PostgreSQL) 30 | #config.active_record.auto_explain_threshold_in_seconds = 0.5 31 | 32 | # Do not compress assets 33 | config.assets.compress = false 34 | 35 | # Expands the lines which load the assets 36 | config.assets.debug = true 37 | 38 | config.eager_load = false 39 | end 40 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_files = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | # config.action_mailer.delivery_method = :test 31 | 32 | # Raise exception on mass assignment protection for Active Record models 33 | # config.active_record.mass_assignment_sanitizer = :strict 34 | 35 | # Print deprecation notices to the stderr 36 | config.active_support.deprecation = :stderr 37 | 38 | config.eager_load = false 39 | 40 | config.active_support.test_order = :random 41 | end 42 | -------------------------------------------------------------------------------- /test/i18n_viz_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'test_helper' 3 | 4 | class I18nVizIntegrationTest < ActionDispatch::IntegrationTest 5 | test 'translate without i18n_viz url parameter' do 6 | visit "/test" 7 | 8 | assert !page.has_content?("--hello--") 9 | assert !page.has_css?("#i18n_viz_tooltip") 10 | assert !page.has_selector?("body > script", visible: false) 11 | assert !page.has_selector?("body > style", visible: false) 12 | end 13 | 14 | test 'translate with i18n_viz url parameter' do 15 | visit "/test?i18n_viz=1" 16 | 17 | assert page.has_selector? "body > script", visible: false 18 | assert page.has_selector?("body > style", visible: false) 19 | assert page.has_content?("--hello--") 20 | assert !page.has_css?(".i18n-viz") 21 | assert !page.has_css?("#i18n_viz_tooltip") 22 | 23 | assert page.has_content?("bar") 24 | assert !page.has_content?("--foo--") 25 | 26 | assert page.has_content?("foo") 27 | end 28 | 29 | test 'translate with i18n_viz as a cookie' do 30 | page.driver.browser.set_cookie("i18n_viz=true") 31 | 32 | visit "/test" 33 | 34 | assert page.has_selector? "body > script", visible: false 35 | assert page.has_selector?("body > style", visible: false) 36 | assert page.has_content?("--hello--") 37 | assert !page.has_css?(".i18n-viz") 38 | assert !page.has_css?("#i18n_viz_tooltip") 39 | 40 | assert page.has_content?("bar") 41 | assert !page.has_content?("--foo--") 42 | 43 | assert page.has_content?("foo") 44 | end 45 | end 46 | 47 | class I18nVizJavascriptIntegrationTest < ActionDispatch::IntegrationTest 48 | setup do 49 | Capybara.current_driver = Capybara.javascript_driver # :selenium by default 50 | end 51 | 52 | test 'translate without i18n_viz url parameter' do 53 | visit "/test" 54 | 55 | assert !page.has_content?("--hello--") 56 | assert !page.has_css?(".i18n-viz") 57 | end 58 | 59 | test 'translate with i18n_viz url parameter' do 60 | visit "/test?i18n_viz=1" 61 | 62 | assert !page.has_content?("--hello--") # should be removed by js 63 | assert page.has_css?(".i18n-viz") 64 | 65 | first('.i18n-viz').hover 66 | 67 | assert page.has_link?('hello', :href => "https://webtranslateit.com/en/projects/xxx/locales/en..de/strings?utf8=✓&s=hello") 68 | assert page.has_css?("#i18n_viz_tooltip") 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/javascripts/i18n_viz_spec.coffee: -------------------------------------------------------------------------------- 1 | require "/javascripts/jquery.js" 2 | require "/javascripts/i18n_viz.js" 3 | 4 | describe "extractI18nKeysFromText()", () -> 5 | it "should return i18n key when 1 present", () -> 6 | expect(window.I18nViz.extractI18nKeysFromText("some text --i18n.key--")).toEqual(["i18n.key"]) 7 | 8 | it "should return null with no keys present", () -> 9 | expect(window.I18nViz.extractI18nKeysFromText("some text --i18n.key-- more text --another.key-- end")).toEqual(["i18n.key", "another.key"]) 10 | 11 | it "should return null with no keys present", () -> 12 | expect(window.I18nViz.extractI18nKeysFromText("some regular test with two dashes -- in the middle")).toBeNull() 13 | 14 | describe "jQuery extensions", () -> 15 | $jquery_element = null 16 | 17 | describe "with i18n jQuery element", () -> 18 | beforeEach () -> 19 | $jquery_element = $("
some text --i18n.key--
") 20 | 21 | describe "$.fn.clearI18nText()", () -> 22 | it "should clear i18n keys from textnode", () -> 23 | $jquery_element.clearI18nText() 24 | expect($jquery_element.text()).toEqual("some text ") 25 | 26 | it "should keep child HTML tags", () -> 27 | $jquery_element.append($("some more text --i18n.key2--")) 28 | $jquery_element.clearI18nText() 29 | expect($jquery_element.text()).toEqual("some text some more text ") 30 | expect($jquery_element.has('span')).toBeTruthy() 31 | expect($jquery_element.find('span').text()).toEqual("some more text ") 32 | 33 | describe "$.fn.enrichWithI18nData()", () -> 34 | it "should enrich element with i18n data", () -> 35 | $jquery_element.enrichWithI18nData() 36 | expect($jquery_element.hasClass("i18n-viz")).toBeTruthy() 37 | expect($jquery_element.data("i18n-keys")).toEqual(["i18n.key"]) 38 | 39 | describe "with non-i18n jQuery element", () -> 40 | beforeEach () -> 41 | $jquery_element = $("
some text
") 42 | 43 | describe "$.fn.clearI18nText()", () -> 44 | it "should not change anything", () -> 45 | text_before = $jquery_element.text() 46 | $jquery_element.clearI18nText() 47 | expect($jquery_element.text()).toEqual(text_before) 48 | 49 | describe "$.fn.enrichWithI18nData()", () -> 50 | it "should not enrich element with i18n data", () -> 51 | $jquery_element.enrichWithI18nData() 52 | expect($jquery_element.hasClass("i18n-viz")).toBeFalsy() 53 | expect($jquery_element.data("i18n-keys")).toBeUndefined() 54 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_files = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to Rails.root.join("public/assets") 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Prepend all log lines with the following tags 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 49 | # config.assets.precompile += %w( search.js ) 50 | 51 | # Disable delivery errors, bad email addresses will be ignored 52 | # config.action_mailer.raise_delivery_errors = false 53 | 54 | # Enable threaded mode 55 | # config.threadsafe! 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation can not be found) 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners 62 | config.active_support.deprecation = :notify 63 | 64 | # Log the query plan for queries taking more than this (works 65 | # with SQLite, MySQL, and PostgreSQL) 66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 67 | 68 | config.eager_load = true 69 | end 70 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path('../boot', __FILE__) 3 | 4 | #require 'rails/all' 5 | require "action_controller/railtie" 6 | #require "action_mailer/railtie" 7 | #require "active_resource/railtie" 8 | require "rails/test_unit/railtie" 9 | require "sprockets/railtie" 10 | 11 | 12 | Bundler.require(:default, Rails.env) 13 | require "i18n_viz" 14 | require "jquery-rails" 15 | require "coffee_script" 16 | 17 | module Dummy 18 | class Application < Rails::Application 19 | # Settings in config/environments/* take precedence over those specified here. 20 | # Application configuration should go into files in config/initializers 21 | # -- all .rb files in that directory are automatically loaded. 22 | 23 | # Custom directories with classes and modules you want to be autoloadable. 24 | # config.autoload_paths += %W(#{config.root}/extras) 25 | 26 | # Only load the plugins named here, in the order given (default is alphabetical). 27 | # :all can be used as a placeholder for all plugins not explicitly named. 28 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 29 | 30 | # Activate observers that should always be running. 31 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 32 | 33 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 34 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 35 | # config.time_zone = 'Central Time (US & Canada)' 36 | 37 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 38 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 39 | # config.i18n.default_locale = :de 40 | 41 | # Configure the default encoding used in templates for Ruby 1.9. 42 | config.encoding = "utf-8" 43 | 44 | # Configure sensitive parameters which will be filtered from the log file. 45 | config.filter_parameters += [:password] 46 | 47 | # Use SQL instead of Active Record's schema dumper when creating the database. 48 | # This is necessary if your schema can't be completely dumped by the schema dumper, 49 | # like if you have constraints or database-specific column types 50 | # config.active_record.schema_format = :sql 51 | 52 | # Enforce whitelist mode for mass assignment. 53 | # This will create an empty whitelist of attributes available for mass-assignment for all models 54 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 55 | # parameters by using an attr_accessible or attr_protected declaration. 56 | # config.active_record.whitelist_attributes = true 57 | 58 | # Enable the asset pipeline 59 | config.assets.compress = false 60 | config.assets.debug = true 61 | config.assets.enabled = true 62 | config.assets.initialize_on_precompile = false 63 | 64 | # Version of your assets, change this if you want to expire all your assets 65 | config.assets.version = '1.0' 66 | 67 | config.middleware.use(I18nViz::Middleware) do |viz| 68 | viz.external_tool_url = "https://webtranslateit.com/en/projects/xxx/locales/en..de/strings?utf8=✓&s=" 69 | end 70 | end 71 | end 72 | 73 | -------------------------------------------------------------------------------- /assets/javascripts/i18n_viz.js: -------------------------------------------------------------------------------- 1 | $.fn.textNodes = function() { 2 | 3 | var e; 4 | try { 5 | return $(this).contents().filter(function() { 6 | var err; 7 | try { 8 | return this.nodeType === 3; 9 | } catch (_error) { 10 | err = _error; 11 | return false; 12 | } 13 | }); 14 | } catch (_error) { 15 | e = _error; 16 | return $([]); 17 | } 18 | }; 19 | 20 | window.I18nViz.extractI18nKeysFromText = function(text) { 21 | var keys; 22 | keys = text.match(window.I18nViz.global_regex); 23 | if (keys) { 24 | keys.forEach(function(value, index) { 25 | return keys[index] = value.replace(/--/g, ""); 26 | }); 27 | return keys; 28 | } else { 29 | return null; 30 | } 31 | }; 32 | 33 | $.fn.enrichWithI18nData = function() { 34 | var $i18n_element, i18n_keys; 35 | $i18n_element = $(this); 36 | i18n_keys = window.I18nViz.extractI18nKeysFromText($i18n_element.text()); 37 | if (i18n_keys !== null) { 38 | $i18n_element.addClass("i18n-viz").data("i18n-keys", i18n_keys); 39 | } 40 | return $i18n_element; 41 | }; 42 | 43 | $.fn.clearI18nText = function() { 44 | var $el; 45 | $el = $(this); 46 | $el.textNodes().each(function(index, node) { 47 | node.textContent = node.textContent.replace(I18nViz.global_regex, ""); 48 | }); 49 | return $el; 50 | }; 51 | 52 | $.extend($.expr[':'], { 53 | 'i18n-textnode': function(el) { 54 | return window.I18nViz.regex.test($(el).textNodes().text()); 55 | }, 56 | 'i18n-value-placeholder': function(el) { 57 | return window.I18nViz.regex.test($(el).val()) || I18nViz.regex.test($(el).attr('placeholder')); 58 | } 59 | }); 60 | 61 | $(document).ready(function() { 62 | $("body").append('
...
').click(function() { 63 | return $("#i18n_viz_tooltip").hide(); 64 | }); 65 | $.fn.initGUI = function() { 66 | var $i18n_element, keys; 67 | $i18n_element = $(this); 68 | keys = $i18n_element.data("i18n-keys"); 69 | $i18n_element.mouseenter(function() { 70 | var $tooltip, left, top; 71 | $tooltip = $("#i18n_viz_tooltip"); 72 | top = $i18n_element.offset().top - $tooltip.outerHeight(); 73 | left = $i18n_element.offset().left; 74 | if (top < 0) { 75 | top = $i18n_element.offset().top + $i18n_element.height() + 10; 76 | } 77 | $tooltip.html(''); 78 | keys.forEach(function(value) { 79 | if (window.I18nViz.external_tool_url.length > 0) { 80 | return $tooltip.append('' + value + ''); 81 | } else { 82 | return $tooltip.append("" + value + ""); 83 | } 84 | }); 85 | return $tooltip.css({ 86 | top: top, 87 | left: left 88 | }).show(); 89 | }); 90 | return $i18n_element; 91 | }; 92 | $(":i18n-textnode").each(function() { 93 | return $(this).enrichWithI18nData().clearI18nText().initGUI(); 94 | }); 95 | return $("input:i18n-value-placeholder").each(function() { 96 | var $i18n_input_element, cleared_input_value, cleared_placeholder_value, input_value, placeholder_value; 97 | $i18n_input_element = $(this); 98 | input_value = $i18n_input_element.val(); 99 | placeholder_value = $i18n_input_element.attr('placeholder'); 100 | $i18n_input_element.enrichWithI18nData(); 101 | cleared_input_value = input_value.replace(window.I18nViz.global_regex, ""); 102 | $(this).val(cleared_input_value); 103 | if (placeholder_value) { 104 | cleared_placeholder_value = placeholder_value.replace(window.I18nViz.global_regex, ""); 105 | return $(this).attr('placeholder', cleared_placeholder_value); 106 | } 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I18nViz 2 | 3 | [![Build Status](https://secure.travis-ci.org/railslove/i18n_viz.png?branch=master)](http://travis-ci.org/railslove/i18n_viz) 4 | 5 | Gem to visualize i18n strings within anay rails/ruby project using the commonly used [i18n](https://github.com/svenfuchs/i18n) gem. 6 | 7 | **Problem**: 8 | 9 | It is very difficult for non-developers (e.g. translators and product managers) working with i18n Rails apps to make the connection from a string within the app to the correct i18n key to edit the corresponding translation. "I would like to tweak the wording of the subtitle on this page, what string do I need to edit?" 10 | 11 | **Solution**: 12 | 13 | My solution is to hack the i18n keys into the frontend, so that the keys can be displayed as nice overlay tooltips on top of their strings right within the app. 14 | 15 | If you you use a translation management service, such as http://webtranslateit.com, https://lingohub.com, http://localeapp.com/, you get the additional benefit of clickable links that point you right to the correct string to edit within your translation tool. 16 | 17 | 18 | ## Upgrading from < 0.4.x 19 | 20 | 1.0.0 was basically a whole new approach how i18n hooks into your application, 21 | so if you're using a version below 1.0, this is for you: 22 | 23 | ### Remove JS 24 | If you required the i18n_viz javascript somewhere, remove it :) 25 | ``` 26 | // = require i18n_viz 27 | ``` 28 | 29 | ### Remove CCS 30 | If you required the i18n_viz stylesheet somewhere, remove it :) 31 | ``` 32 | /* = require i18n_viz.css */ 33 | ``` 34 | 35 | ### Remove initializer 36 | 37 | Remove the initializer from `config/i18n_viz.rb` 38 | 39 | ### THEN follow the new installation instructions 40 | 41 | 42 | ## Requirements 43 | 44 | * [i18n](https://github.com/svenfuchs/i18n) gem 45 | * jQuery 46 | * Rails > 3.1+ 47 | * CoffeeScript 48 | 49 | However, with just a little bit of manual work, you should be able to get the whole thing to run without Rails and CoffeeScript. Just let me know if you need help. 50 | 51 | ## Installation 52 | 53 | ### 1. Install the gem 54 | 55 | Add the following line to your `Gemfile` and run `bundle install`: 56 | 57 | gem 'i18n_viz' 58 | 59 | ### 2. Insert the middleware 60 | 61 | add do e.g. `config/application.rb`: 62 | 63 | config.middleware.use(I18nViz::Middleware) 64 | 65 | if you want to configure an external tool 66 | 67 | config.middleware.use(I18nViz::Middleware) do |viz| 68 | viz.external_tool_url = "https://webtranslateit.com/en/projects/xxx/locales/en..de/strings?utf8=✓&s=" 69 | end 70 | 71 | if you want to configure CSS overrides 72 | 73 | config.middleware.use(I18nViz::Middleware) do |viz| 74 | viz.css_override = "/* valid CSS text here */" 75 | end 76 | 77 | 78 | ### 3. Browse to http://localhost:3000?i18n_viz=true 79 | 80 | Add the `i18n_viz=true` parameter to visualize the translatable segments. 81 | 82 | Alternatively, a cookie with the name `i18n_viz` can be set to any value to achieve the same effect. 83 | 84 | 85 | ## How it works 86 | 87 | The gem works by overwriting the `t()` and `translate()` helpers in your rails app to add the key of the i18n string after the actual translated content: 88 | 89 | en: 90 | examples: 91 | my_string: "My internationalized string" 92 | foo: "bar" 93 | 94 | %span= "#{t("examples.my_string")} : #{t("examples.foo")}" 95 | 96 | Will result in 97 | 98 | My internationalized string--examples.my_string-- : bar--examples.foo-- 99 | 100 | The i18n_viz Javascript then parses this and enriches it into: 101 | 102 | My internationalized string : bar 103 | 104 | The so enriched elements then get nice little tooltips attached with the i18n keys and possibly links to where they can be found/changed. 105 | 106 | 107 | ## Gotchas & Limitations 108 | 109 | #### Works only in the view layer 110 | 111 | The keys will currently only work for strings that are translated in the view layer using the `translate()` and `t()` i18n view helpers. If you translate a string the model layer using `I18n.translate` method directly (e.g. in ActiveRecord validations) the keys are not displayed in the frontend. 112 | 113 | 114 | #### Disable i18n_viz if you require certain strings to remain untouched 115 | 116 | For certain special strings within your app, the added `--key--` could potentially break your custom logic. For those cases you need to call the `t()` method the additional `i18n_viz: false` parameter. 117 | 118 | Example cases include: 119 | 120 | **Iterating overy keys** 121 | 122 | de: 123 | types: 124 | foo: "Foo" 125 | bar: "Bar" 126 | 127 | - t("types").each do |key, string| 128 | .. do something wit the key .. 129 | 130 | **JS data-attributes** 131 | 132 | de: 133 | date: 134 | js_format: "dd.mm.yyyy" 135 | 136 | 137 | %body{:"data-date-format" => t("date.js_format")} 138 | 139 | Will result in this broken output: 140 | 141 | 142 | 143 | 144 | ## Thanks 145 | 146 | Big thanks to my employer [Railslove](http://railslove.com) for supporting my open source work and to everybody who helped me. 147 | 148 | 149 | ## License 150 | 151 | This project is under MIT-LICENSE. 152 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.all 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.org/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- assets 160 | | |-- images 161 | | |-- javascripts 162 | | `-- stylesheets 163 | | |-- controllers 164 | | |-- helpers 165 | | |-- mailers 166 | | |-- models 167 | | `-- views 168 | | `-- layouts 169 | |-- config 170 | | |-- environments 171 | | |-- initializers 172 | | `-- locales 173 | |-- db 174 | |-- doc 175 | |-- lib 176 | | `-- tasks 177 | |-- log 178 | |-- public 179 | |-- script 180 | |-- test 181 | | |-- fixtures 182 | | |-- functional 183 | | |-- integration 184 | | |-- performance 185 | | `-- unit 186 | |-- tmp 187 | | |-- cache 188 | | |-- pids 189 | | |-- sessions 190 | | `-- sockets 191 | `-- vendor 192 | |-- assets 193 | `-- stylesheets 194 | `-- plugins 195 | 196 | app 197 | Holds all the code that's specific to this particular application. 198 | 199 | app/assets 200 | Contains subdirectories for images, stylesheets, and JavaScript files. 201 | 202 | app/controllers 203 | Holds controllers that should be named like weblogs_controller.rb for 204 | automated URL mapping. All controllers should descend from 205 | ApplicationController which itself descends from ActionController::Base. 206 | 207 | app/models 208 | Holds models that should be named like post.rb. Models descend from 209 | ActiveRecord::Base by default. 210 | 211 | app/views 212 | Holds the template files for the view that should be named like 213 | weblogs/index.html.erb for the WeblogsController#index action. All views use 214 | eRuby syntax by default. 215 | 216 | app/views/layouts 217 | Holds the template files for layouts to be used with views. This models the 218 | common header/footer method of wrapping views. In your views, define a layout 219 | using the layout :default and create a file named default.html.erb. 220 | Inside default.html.erb, call <% yield %> to render the view using this 221 | layout. 222 | 223 | app/helpers 224 | Holds view helpers that should be named like weblogs_helper.rb. These are 225 | generated for you automatically when using generators for controllers. 226 | Helpers can be used to wrap functionality for your views into methods. 227 | 228 | config 229 | Configuration files for the Rails environment, the routing map, the database, 230 | and other dependencies. 231 | 232 | db 233 | Contains the database schema in schema.rb. db/migrate contains all the 234 | sequence of Migrations for your schema. 235 | 236 | doc 237 | This directory is where your application documentation will be stored when 238 | generated using rake doc:app 239 | 240 | lib 241 | Application specific libraries. Basically, any kind of custom code that 242 | doesn't belong under controllers, models, or helpers. This directory is in 243 | the load path. 244 | 245 | public 246 | The directory available for the web server. Also contains the dispatchers and the 247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 248 | server. 249 | 250 | script 251 | Helper scripts for automation and generation. 252 | 253 | test 254 | Unit and functional tests along with fixtures. When using the rails generate 255 | command, template test files will be generated for you and placed in this 256 | directory. 257 | 258 | vendor 259 | External libraries that the application depends on. Also includes the plugins 260 | subdirectory. If the app has frozen rails, those gems also go here, under 261 | vendor/rails/. This directory is in the load path. 262 | --------------------------------------------------------------------------------