i18n_locale_editor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +73 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/app/controllers/translations_controller.rb +45 -0
- data/app/helpers/translations_helper.rb +53 -0
- data/app/models/translation.rb +59 -0
- data/app/views/layouts/translations.erb +27 -0
- data/app/views/translations/index.html.erb +42 -0
- data/config/routes.rb +5 -0
- data/lib/hash_tools.rb +105 -0
- data/lib/i18n_locale_editor.rb +20 -0
- data/lib/translate_monkey_patch.rb +37 -0
- data/public/i18n-locale-editor.js +167 -0
- data/rails/init.rb +20 -0
- data/spec/i18n_locale_editor_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +148 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Adam Kliment
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= Rails i18n locale editor
|
2
|
+
|
3
|
+
Proof of concept for live locale editor fo Ruby on Rails
|
4
|
+
|
5
|
+
|
6
|
+
== Features
|
7
|
+
* edit translations in-place in overlay window right on your live site
|
8
|
+
* cool firebug-like translation key selector for localized strings
|
9
|
+
* ajax lazy updating for better user experience
|
10
|
+
* taranslations listing (index)
|
11
|
+
* select which locales to show in translations index
|
12
|
+
* show only keys with missing translations
|
13
|
+
* unobtrusive JS inclusion - if jQuery is not included in project and async autoloads it from CDN as noConflict(), then loads itself
|
14
|
+
* tested in Firefox and Chromium
|
15
|
+
* tested on rails 2.3.8, 2.3.2 with bundler see: http://gembundler.com/rails23.html
|
16
|
+
* not designed as production CMS, it's for developers
|
17
|
+
|
18
|
+
== Installation
|
19
|
+
|
20
|
+
add to your Gemfile:
|
21
|
+
|
22
|
+
<tt>gem "i18n_locale_editor"</tt>
|
23
|
+
|
24
|
+
run:
|
25
|
+
|
26
|
+
<tt>bundle install</tt>
|
27
|
+
|
28
|
+
add to your layout's <HEAD> section:
|
29
|
+
|
30
|
+
<tt>=include_i18n_editor</tt>
|
31
|
+
|
32
|
+
start server with environment variable:
|
33
|
+
|
34
|
+
<tt>I18N_EDITOR=true script/server</tt>
|
35
|
+
|
36
|
+
|
37
|
+
== TODO
|
38
|
+
|
39
|
+
* tests
|
40
|
+
* code review and documentation
|
41
|
+
* rails 3 support
|
42
|
+
* test it on real-life translator's workflow to save clicks and improve efficiency (maybe add 'commit and next' to editor)
|
43
|
+
* auto copy js asset and include by gem version in init.rb
|
44
|
+
* when open in editor is requested, add locale from key to be shown on index if it is not set in session yet
|
45
|
+
* when open in editor is requeseed and "show only missing" is enabled, check if the requested key is in the result set, if not cancel the fileter and retry
|
46
|
+
* it will copy javascript assets to RAILS_ROOT/public/javascripts/ on every init, find some better way
|
47
|
+
* raise if not vaild html accepted as transalation
|
48
|
+
* show key name as title in modal window
|
49
|
+
* move all javascript functions to i18n namespace to prevent colision
|
50
|
+
* make hash_tools as inherited class ExtendedHash from Hash (eradicate monkey patching)
|
51
|
+
* extract translation controller name, model name, routing nammespace to some config and make it dynamic
|
52
|
+
* ESC to close modal window
|
53
|
+
* possibility to load another translations in modal
|
54
|
+
* toggle wym editor in modal (cool)
|
55
|
+
* change localization key from id to rel in span I18n monkey patch
|
56
|
+
* escape path for eval in hash_tools.rb (!!!)
|
57
|
+
* translation workflow design
|
58
|
+
* translation key is held in span id="en.translation.key". check if the 'id' attribute can contain dots
|
59
|
+
* title, buttons, select options are broken - filter them out
|
60
|
+
* create tools for refactoring - especially path rename
|
61
|
+
* paginantion on index
|
62
|
+
* list missing translations
|
63
|
+
* api for external access
|
64
|
+
* scopes support for api (how)
|
65
|
+
* export all locales to csv (it's done, but not included in this gem)
|
66
|
+
* import all locales from csv (it's done, but not included in this gem)
|
67
|
+
* key hitcounter
|
68
|
+
* test in Opera, IE and other browsers...
|
69
|
+
* review these issues and move them to the github's task tracker :)
|
70
|
+
|
71
|
+
== Copyright
|
72
|
+
|
73
|
+
Copyright (c) 2011 Adam Kliment, Virtualmater s.r.o. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "i18n_locale_editor"
|
8
|
+
gem.summary = %Q{Rails i18n locale editor}
|
9
|
+
gem.description = %Q{Rails i18n locale web editor as plugin}
|
10
|
+
gem.email = "adam.kliment@virtualmaster.cz"
|
11
|
+
gem.homepage = "http://github.com/n1k/i18n_locale_editor"
|
12
|
+
gem.authors = ["Adam Kliment"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency "ya2yaml", ">=0.26"
|
15
|
+
gem.add_dependency "rails", ">= 2.3.0"
|
16
|
+
gem.add_dependency "i18n", ">= 0"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
33
|
+
spec.rcov = true
|
34
|
+
end
|
35
|
+
|
36
|
+
task :spec => :check_dependencies
|
37
|
+
|
38
|
+
task :default => :spec
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "i18n_locale_editor #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class TranslationsController < ApplicationController
|
2
|
+
before_filter :set_language, :except => [:index]
|
3
|
+
skip_before_filter :verify_authenticity_token, :only => [:set_value]
|
4
|
+
#TODO remove
|
5
|
+
skip_before_filter :admin_required, :login_required
|
6
|
+
layout 'translations'
|
7
|
+
|
8
|
+
|
9
|
+
def index
|
10
|
+
session[:i18n_locale_editor_translations] = [I18n.available_locales.first] if session[:i18n_locale_editor_translations].blank?
|
11
|
+
session[:i18n_show_only_incomplete] = false if session[:i18n_show_only_incomplete].blank?
|
12
|
+
|
13
|
+
@translations = Translation.merge_locales session[:i18n_locale_editor_translations], session[:i18n_show_only_incomplete]
|
14
|
+
|
15
|
+
respond_to do |format|
|
16
|
+
format.html # index.html.erb
|
17
|
+
format.xml { render :xml => @translations }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_value
|
22
|
+
value = Translation.all(@language).find_by_path(params[:path])
|
23
|
+
render :text => value
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_value
|
27
|
+
Translation.update_by_path(@language, params[:path], params[:value])
|
28
|
+
I18n.reload!
|
29
|
+
render :text => "OK"
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_translations
|
33
|
+
session[:i18n_locale_editor_translations] = params[:translations]
|
34
|
+
session[:i18n_show_only_incomplete] = true if params[:show_only_incomplete] == "true"
|
35
|
+
session[:i18n_show_only_incomplete] = false if params[:show_only_incomplete] == "false"
|
36
|
+
redirect_to :action => 'index'
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def set_language
|
42
|
+
@language = params[:path].split(".").first if not params[:path].nil?
|
43
|
+
# @language = I18n.locale if not I18n.available_locales.include? @language
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Methods added to this helper will be available to all templates in the application.
|
2
|
+
module TranslationsHelper
|
3
|
+
def include_i18n_editor
|
4
|
+
return nil if not ENV['I18N_EDITOR'].to_s == "true"
|
5
|
+
javascript = "
|
6
|
+
// i18n locale editor
|
7
|
+
// =================
|
8
|
+
// - lazy loads jQuery from jQuery's CDN if it is not defined and declares it as $j if $js is undefined
|
9
|
+
// - waits for jQuery initialization and then lazy-loads itself
|
10
|
+
// - it expect i18n-locale-editor.js in /public/javascripts
|
11
|
+
|
12
|
+
i18n_locale = '#{I18n.locale.to_s}';
|
13
|
+
|
14
|
+
if(typeof($j) === 'undefined'){
|
15
|
+
var $j;
|
16
|
+
}
|
17
|
+
|
18
|
+
function i18n_lazy_load_js(url){
|
19
|
+
var s = document.createElement('script');
|
20
|
+
s.type = 'text/javascript';
|
21
|
+
s.async = true;
|
22
|
+
s.src = url;
|
23
|
+
var x = document.getElementsByTagName('script')[0];
|
24
|
+
x.parentNode.insertBefore(s, x);
|
25
|
+
}
|
26
|
+
|
27
|
+
function i18n_lazy_loader(){
|
28
|
+
if(! (typeof(jQuery) === 'function')){
|
29
|
+
i18n_lazy_load_js('http://code.jquery.com/jquery-1.5.1.min.js')
|
30
|
+
}
|
31
|
+
interval = setInterval(function(){
|
32
|
+
if(typeof(jQuery) === 'function'){
|
33
|
+
if(typeof($j) === 'undefined'){
|
34
|
+
$j = jQuery.noConflict();
|
35
|
+
}
|
36
|
+
i18n_lazy_load_js('/javascripts/i18n-locale-editor.js')
|
37
|
+
clearInterval(interval);
|
38
|
+
}
|
39
|
+
},100)
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
if(window.attachEvent) {
|
44
|
+
window.attachEvent('onload', i18n_lazy_loader);
|
45
|
+
} else {
|
46
|
+
window.addEventListener('load', i18n_lazy_loader, false);
|
47
|
+
}
|
48
|
+
|
49
|
+
"
|
50
|
+
javascript_tag javascript
|
51
|
+
end
|
52
|
+
end
|
53
|
+
ActionView::Base.send :include, TranslationsHelper
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#LOCED
|
2
|
+
require 'ya2yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class Translation
|
6
|
+
|
7
|
+
def self.all(locale)
|
8
|
+
locale = locale.to_s
|
9
|
+
file = File.join(RAILS_ROOT, "config/locales","#{locale}.yml")
|
10
|
+
hash = YAML::load_file file
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.update_by_path(locale,path,value)
|
14
|
+
locale = locale.to_s
|
15
|
+
|
16
|
+
file = File.join(RAILS_ROOT, "config/locales","#{locale}.yml")
|
17
|
+
hash = YAML::load_file file
|
18
|
+
hash.update_by_path(path,value)
|
19
|
+
File.open(file + ".new",'w') do |f|
|
20
|
+
f.puts hash.ya2yaml
|
21
|
+
end
|
22
|
+
FileUtils.mv(file + ".new", file)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.merge_locales locales_array, only_missing = false
|
26
|
+
#available_locales = I18n.available_locales.map{|loc| loc.to_s}
|
27
|
+
#locales_array.each{|loc| raise "#{loc} locale not found" if not available_locales.include? loc.to_s }
|
28
|
+
|
29
|
+
paths = []
|
30
|
+
translations = {}
|
31
|
+
locales_array.each do |locale|
|
32
|
+
translations[locale] = translation = Translation.all(locale)
|
33
|
+
paths = paths + translation.paths.map{|path| path.gsub(/^#{locale.to_s}\./,"")}
|
34
|
+
end
|
35
|
+
paths = paths.uniq
|
36
|
+
|
37
|
+
aggreg = {}
|
38
|
+
paths.each do |path|
|
39
|
+
aggreg[path] = {}
|
40
|
+
locales_array.each do |locale|
|
41
|
+
aggreg[path][locale] = translations[locale].find_by_path(locale.to_s + '.' + path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if only_missing
|
45
|
+
return aggreg.delete_if do |path,translation|
|
46
|
+
ret = true
|
47
|
+
translation.each do |language, value|
|
48
|
+
ret = false if value.blank?
|
49
|
+
end
|
50
|
+
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
else
|
54
|
+
return aggreg
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<head>
|
4
|
+
<title>Translation editor</title>
|
5
|
+
<meta content="text/html;charset=UTF-8" http-equiv="content-type"/>
|
6
|
+
<style media='all' type='text/css'>
|
7
|
+
body{ font-family: Verdana,Arial,sans-serif; border: 0; margin: 0; padding: 0;}
|
8
|
+
.i18n-translation-editor{padding: 10px}
|
9
|
+
.i18n-translation-key{font-size: 20px; padding-top: 10px}
|
10
|
+
.i18n-locale-label{:font-weight: bold; font-size: 30px; background-color: #111; color: #fff}
|
11
|
+
.i18n-translation-missing{}
|
12
|
+
.i18n-translation-cell{cursor: default}
|
13
|
+
.i18n-highlighted-cell{background-color: #eee;color:#000}
|
14
|
+
.i18n-highlighted-exclamation{font-size: 40px; color: #111; font-weight: bold; padding-left: 10px}
|
15
|
+
</style>
|
16
|
+
|
17
|
+
<%=include_i18n_editor%>
|
18
|
+
|
19
|
+
</head>
|
20
|
+
|
21
|
+
<body>
|
22
|
+
<div class="i18n-translation-editor">
|
23
|
+
<%= yield%>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
27
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<h1>Listing translations</h1>
|
2
|
+
|
3
|
+
<%form_tag url_for({:action => 'set_translations'}), :method => :post do |form|%>
|
4
|
+
<%I18n.available_locales.each do |locale|%>
|
5
|
+
<%= locale%>
|
6
|
+
<%= check_box_tag "translations[]", locale, session[:i18n_locale_editor_translations].include?(locale.to_s) ? true : false %>
|
7
|
+
<%end%>
|
8
|
+
only incomplete rows
|
9
|
+
<%= hidden_field_tag "show_only_incomplete", "false"%>
|
10
|
+
<%= check_box_tag "show_only_incomplete","true", session[:i18n_show_only_incomplete]%>
|
11
|
+
<%= submit_tag :show%>
|
12
|
+
<%end%>
|
13
|
+
<table>
|
14
|
+
<tr>
|
15
|
+
<%session[:i18n_locale_editor_translations].each do |locale|%>
|
16
|
+
<th class="i18n-locale-label"><%= locale%></th>
|
17
|
+
<%end%>
|
18
|
+
</tr>
|
19
|
+
<% @translations.each do |path, translations| %>
|
20
|
+
<tr>
|
21
|
+
<td colspan="<%=translations.length%>">
|
22
|
+
<div class="i18n-translation-key">
|
23
|
+
<%= path%>
|
24
|
+
</div>
|
25
|
+
</td>
|
26
|
+
</tr>
|
27
|
+
<tr>
|
28
|
+
<%translations.each do |language, translation|%>
|
29
|
+
<td class="i18n-translation-cell <%='i18n-translation-missing' if translation.blank?%>">
|
30
|
+
<div>
|
31
|
+
<span class="i18n-translation" id="<%=language.to_s + '.' + path%>"><%=translation%></span>
|
32
|
+
<%if translation.blank?%>
|
33
|
+
<span class="i18n-highlighted-exclamation">!</span>
|
34
|
+
<%end%>
|
35
|
+
</div>
|
36
|
+
</td>
|
37
|
+
<%end%>
|
38
|
+
</tr>
|
39
|
+
<% end %>
|
40
|
+
</table>
|
41
|
+
<br />
|
42
|
+
|
data/config/routes.rb
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
ActionController::Routing::Routes.draw do |map|
|
2
|
+
map.translations 'translations/get_value/:path', :controller => 'translations', :action => 'get_value', :method => :get, :requirements => { :path => /.+/ }
|
3
|
+
map.translations 'translations/set_value/:path', :controller => 'translations', :action => 'set_value', :method => :get, :requirements => { :path => /.+/ }
|
4
|
+
map.translations 'translations/index', :controller => 'translations', :action => 'index'
|
5
|
+
end
|
data/lib/hash_tools.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
class Hash
|
2
|
+
#Finds value in hash by path
|
3
|
+
#example:
|
4
|
+
#hash = {
|
5
|
+
# a => 'foo',
|
6
|
+
# b => {
|
7
|
+
# ba => {
|
8
|
+
# bac => 'bar'
|
9
|
+
# }
|
10
|
+
# }
|
11
|
+
#}
|
12
|
+
#>>>
|
13
|
+
|
14
|
+
def find_by_path(path)
|
15
|
+
e ="self"
|
16
|
+
path_to_a(path).each{|i| e = e+"['#{i}']" }
|
17
|
+
begin
|
18
|
+
eval(e)
|
19
|
+
rescue NoMethodError
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def update_by_path(path, string)
|
26
|
+
return false if find_by_path(path).class == "Hash"
|
27
|
+
create_path_if_not_exist(path)
|
28
|
+
e ="self"
|
29
|
+
path_a = path_to_a(path)
|
30
|
+
path_a.each{|i| e = e+"['#{i}']" }
|
31
|
+
string = string.gsub(/^[ \t]+/,"")
|
32
|
+
e = e+"='#{string}'"
|
33
|
+
begin
|
34
|
+
eval(e)
|
35
|
+
return true
|
36
|
+
rescue => e
|
37
|
+
return [false, e]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def update_by_path_wo_parents(path, string)
|
43
|
+
return false if find_by_path(path).class.to_s == "Hash" && find_by_path(path).length > 0
|
44
|
+
e ="self"
|
45
|
+
path_a = path_to_a(path)
|
46
|
+
path_a.each{|i| e = e+"['#{i}']" }
|
47
|
+
#string = string.gsub(/^[ \t]+/,"")
|
48
|
+
e = e+"=string"
|
49
|
+
begin
|
50
|
+
eval(e)
|
51
|
+
return true
|
52
|
+
rescue
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_empty_node(path)
|
58
|
+
return false if not find_by_path(path).nil?
|
59
|
+
update_by_path_wo_parents(path, {})
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_path_if_not_exist(path)
|
63
|
+
aa = path_to_a(path)
|
64
|
+
new_aa = []
|
65
|
+
aa.each do |a|
|
66
|
+
new_aa << a
|
67
|
+
create_empty_node(a_to_path(new_aa))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def nodes_array(path = [])
|
72
|
+
ret = []
|
73
|
+
self.each do |key,value|
|
74
|
+
if value.class == Hash
|
75
|
+
ret = ret + value.nodes_array(path + [key])
|
76
|
+
else
|
77
|
+
ret << path + [key]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
ret
|
81
|
+
end
|
82
|
+
|
83
|
+
def paths
|
84
|
+
self.nodes_array.map{|i| i.join(".")}
|
85
|
+
end
|
86
|
+
|
87
|
+
def next_path(path)
|
88
|
+
paths = self.paths
|
89
|
+
paths[paths.index(path) + 1]
|
90
|
+
end
|
91
|
+
|
92
|
+
def previous_path(path)
|
93
|
+
paths = self.paths
|
94
|
+
paths[paths.index(path) - 1]
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def path_to_a(string)
|
99
|
+
string.split(".")
|
100
|
+
end
|
101
|
+
|
102
|
+
def a_to_path(array)
|
103
|
+
array.join(".")
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'hash_tools'
|
2
|
+
require 'translate_monkey_patch'
|
3
|
+
require 'ftools'
|
4
|
+
|
5
|
+
if ENV['I18N_EDITOR']== "true"
|
6
|
+
src = File.join(directory,'public',"i18n-locale-editor.js")
|
7
|
+
dst = File.join(RAILS_ROOT,'public', 'javascripts')
|
8
|
+
File.copy(src,dst)
|
9
|
+
end
|
10
|
+
|
11
|
+
%w{ models controllers helpers }.each do |dir|
|
12
|
+
path = File.join(directory, 'app',dir)
|
13
|
+
$LOAD_PATH << path
|
14
|
+
ActiveSupport::Dependencies.load_paths << path
|
15
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
require File.join(directory, "app", "helpers", "translations_helper.rb")
|
19
|
+
ActionView::Base.send :include, TranslationsHelper;
|
20
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Don't run in production, it may cause death or serios injury to your layout. :-P
|
2
|
+
#TODO
|
3
|
+
#wrap tag helpers
|
4
|
+
# def text_field
|
5
|
+
# /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.8/lib/action_view/helpers/form_helper.rb
|
6
|
+
# def text_field_tag
|
7
|
+
# /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.8/lib/action_view/helpers/form_tag_helper.rb:
|
8
|
+
|
9
|
+
if ENV['I18N_EDITOR'].to_s == "true"
|
10
|
+
module ActionView
|
11
|
+
module Helpers
|
12
|
+
module TranslationHelper
|
13
|
+
alias :old_translate :translate
|
14
|
+
|
15
|
+
def translate(*args)
|
16
|
+
key = [*args].first
|
17
|
+
key = I18n.locale.to_s + '.' + key.to_s
|
18
|
+
translation = old_translate(*args)
|
19
|
+
translation = " " if translation.blank?
|
20
|
+
'<span id="'+key+'" class="i18n-translation">' + translation + '</span>'
|
21
|
+
end
|
22
|
+
alias :t :translate
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else
|
27
|
+
module ActionView
|
28
|
+
module Helpers
|
29
|
+
module TranslationHelper
|
30
|
+
def include_i18n_tools
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,167 @@
|
|
1
|
+
if(typeof $j === 'undefined'){
|
2
|
+
$j = jQuery.noConflict();
|
3
|
+
}
|
4
|
+
|
5
|
+
function attach_callbacks() {
|
6
|
+
$j('.i18n-translation').each(function(){
|
7
|
+
$j(this).bind('click.i18n_selector',function(event){
|
8
|
+
event.preventDefault();
|
9
|
+
fill_modal($j(this));
|
10
|
+
show_modal();
|
11
|
+
return false;
|
12
|
+
}).bind('mouseover.i18n_selector',
|
13
|
+
function(){
|
14
|
+
$j(this).addClass('i18n-highlighted')
|
15
|
+
$j('#i18n-text').html($j(this).attr('id'))
|
16
|
+
}).bind('mouseout.i18n_selector', function(){
|
17
|
+
$j(this).removeClass('i18n-highlighted')
|
18
|
+
$j('#i18n-text').html('none')
|
19
|
+
})
|
20
|
+
red_button();
|
21
|
+
})
|
22
|
+
|
23
|
+
}
|
24
|
+
|
25
|
+
function detach_callbacks() {
|
26
|
+
$j('.i18n-translation').unbind('click.i18n_selector').unbind('mouseover.i18n_selector').unbind('mouseout.i18n_selector');
|
27
|
+
$j('#i18n-button').css('background-color', 'blue');
|
28
|
+
blue_button();
|
29
|
+
}
|
30
|
+
|
31
|
+
function blue_button() {
|
32
|
+
$j('#i18n-button').bind('click.i18n_attach_callbacks',function(){
|
33
|
+
attach_callbacks();
|
34
|
+
return false;
|
35
|
+
}).html('SELECT')
|
36
|
+
}
|
37
|
+
|
38
|
+
function red_button(){
|
39
|
+
$j('#i18n-button').css('background-color', 'red').html('STOP')
|
40
|
+
$j('#i18n-button').unbind('click.i18n_attach_callbacks')
|
41
|
+
$j('#i18n-button').bind('click.i18n_detach_callbacks',function(){
|
42
|
+
detach_callbacks();
|
43
|
+
return false;
|
44
|
+
})
|
45
|
+
|
46
|
+
}
|
47
|
+
|
48
|
+
function hide_modal(){
|
49
|
+
$j('#i18n-mask, .i18n-window').hide();
|
50
|
+
}
|
51
|
+
|
52
|
+
function fill_modal(element){
|
53
|
+
url = "/translations/index"
|
54
|
+
content = '\
|
55
|
+
<input type="hidden" name="i18n-form-key" value="'+element.attr('id')+'" id="i18n-form-key">\
|
56
|
+
<form onsubmit="i18n_send_form();return false;">\
|
57
|
+
<textarea name="i8n-form-value" id="i18n-form-value">'+element.html()+'</textarea>\
|
58
|
+
</form>\
|
59
|
+
<div><a href="#" onclick="i18n_send_form();return false;">commit change</a></div\
|
60
|
+
<div><a href="'+url+'#'+ element.attr('id') +'" target="_blank">open in editor</a></div>\
|
61
|
+
'
|
62
|
+
$j('#18n-modal-content').html(content)
|
63
|
+
}
|
64
|
+
|
65
|
+
function i18n_send_form(){
|
66
|
+
key = $j('#i18n-form-key').val();
|
67
|
+
value = $j('#i18n-form-value').val();
|
68
|
+
selector = 'span[id="'+key+'"]';
|
69
|
+
$j(selector).html(value);
|
70
|
+
update_value(key, value)
|
71
|
+
hide_modal();
|
72
|
+
}
|
73
|
+
|
74
|
+
function update_value(key,value) {
|
75
|
+
$j('span[id="'+key+'"]').siblings(".i18n-highlighted-exclamation").hide()
|
76
|
+
$j('#i18n-working').show()
|
77
|
+
url= '/translations/set_value/'+key
|
78
|
+
request = $j.ajax({
|
79
|
+
// GET method to be able pass redirects
|
80
|
+
type: 'GET',
|
81
|
+
url: url,
|
82
|
+
data: {value: value},
|
83
|
+
async: true,
|
84
|
+
success: function(){
|
85
|
+
$j('#i18n-working').hide();
|
86
|
+
|
87
|
+
},
|
88
|
+
error: function(){
|
89
|
+
alert('error posting data to url:'+url);
|
90
|
+
$j('#i18n-working').hide()
|
91
|
+
}
|
92
|
+
})
|
93
|
+
}
|
94
|
+
function show_modal(){
|
95
|
+
element = $j("#i18n-dialog");
|
96
|
+
var maskHeight = $j(document).height();
|
97
|
+
var maskWidth = $j(window).width();
|
98
|
+
$j('#i18n-mask').css({'width':maskWidth,'height':maskHeight});
|
99
|
+
$j('#i18n-mask').show();
|
100
|
+
var winH = $j(window).height();
|
101
|
+
var winW = $j(window).width();
|
102
|
+
element.css('top', winH/2-element.height()/2);
|
103
|
+
element.css('left', winW/2-element.width()/2);
|
104
|
+
element.show()
|
105
|
+
}
|
106
|
+
|
107
|
+
function i18n_initialize_selectors() {
|
108
|
+
add_assets();
|
109
|
+
$j("#i18n-button").show()
|
110
|
+
blue_button();
|
111
|
+
$j('#i18n-modal-close').click(function(){
|
112
|
+
hide_modal();
|
113
|
+
})
|
114
|
+
}
|
115
|
+
|
116
|
+
function i18n_initialize_editor(){
|
117
|
+
add_assets();
|
118
|
+
$j('.i18n-translation-cell').click(function(){
|
119
|
+
fill_modal($j('.i18n-translation', this));
|
120
|
+
show_modal();
|
121
|
+
}).hover(function(){
|
122
|
+
$j(this).addClass('i18n-highlighted-cell');
|
123
|
+
}, function(){
|
124
|
+
$j(this).removeClass('i18n-highlighted-cell');
|
125
|
+
}
|
126
|
+
)
|
127
|
+
}
|
128
|
+
|
129
|
+
function add_assets() {
|
130
|
+
$j('\
|
131
|
+
<div id= "i18n-placeholder" >\
|
132
|
+
<div id="i18n-text"> </div>\
|
133
|
+
<a id="i18n-button" href="#" style="display: none"> </a>\
|
134
|
+
<div id="i18n-working" style="display: none">Zzz...</div>\
|
135
|
+
</div>\
|
136
|
+
\
|
137
|
+
<div id="i18n-boxes">\
|
138
|
+
<div id="i18n-dialog" class="i18n-window">\
|
139
|
+
<div id="18n-modal-content"></div>\
|
140
|
+
<a href="#" id="i18n-modal-close" onclick="hide_modal();return false;">close window</a>\
|
141
|
+
</div>\
|
142
|
+
<div id="i18n-mask"></div>\
|
143
|
+
</div>\
|
144
|
+
').appendTo("body")
|
145
|
+
$j('\
|
146
|
+
<style type="text/css"> \
|
147
|
+
.i18n-highlighted{ cursor: default; outline: 3px solid yellow} \
|
148
|
+
#i18n-text{ text-align: right; }\
|
149
|
+
#i18n-working{ text-align: center;line-height: 30px;width: 50px; height: 30px; float: right; background-color: #444; color: white;}\
|
150
|
+
#i18n-button{ text-align: center;line-height: 30px;width: 50px; height: 30px; float: right; background-color: #111; color: white;}\
|
151
|
+
#i18n-button a{ color: white;}\
|
152
|
+
#i18n-button a:hover{ color: white;}\
|
153
|
+
#i18n-placeholder{font-size: 12px;font-family: Verdana, Arial, sans-serif; margin:auto; position: fixed; z-index: 10000; right: 0px; bottom: 0px;}\
|
154
|
+
#i18n-mask { position:absolute; top: 0; z-index:9998; background-color:#000; display:none;opacity:0.4;filter:alpha(opacity=40) } \
|
155
|
+
#i18n-boxes .i18n-window { font-size: 12px;font-family: Verdana, Arial, sans-serif;position:fixed;width:600px;height:400px;display:none;z-index:9999;padding:20px;} \
|
156
|
+
#i18n-boxes #i18n-dialog { width:580px; height:400px; background-color: white; }\
|
157
|
+
#i18n-boxes #i18n-dialog textarea { width:560px; height:300px; background-color: white; }\
|
158
|
+
</style> \
|
159
|
+
').appendTo("head");
|
160
|
+
}
|
161
|
+
|
162
|
+
if(! ($j('.i18n-translation-editor').length == 0)){
|
163
|
+
i18n_initialize_editor();
|
164
|
+
} else{
|
165
|
+
i18n_initialize_selectors();
|
166
|
+
}
|
167
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'hash_tools'
|
2
|
+
require 'translate_monkey_patch'
|
3
|
+
require 'ftools'
|
4
|
+
|
5
|
+
if ENV['I18N_EDITOR']== "true"
|
6
|
+
src = File.join(directory,'public',"i18n-locale-editor.js")
|
7
|
+
dst = File.join(RAILS_ROOT,'public', 'javascripts')
|
8
|
+
File.copy(src,dst)
|
9
|
+
end
|
10
|
+
|
11
|
+
%w{ models controllers helpers }.each do |dir|
|
12
|
+
path = File.join(directory, 'app',dir)
|
13
|
+
$LOAD_PATH << path
|
14
|
+
ActiveSupport::Dependencies.load_paths << path
|
15
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
require File.join(directory, "app", "helpers", "translations_helper.rb")
|
19
|
+
ActionView::Base.send :include, TranslationsHelper;
|
20
|
+
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: i18n_locale_editor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adam Kliment
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-07 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: ya2yaml
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 63
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 26
|
49
|
+
version: "0.26"
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rails
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 3
|
64
|
+
- 0
|
65
|
+
version: 2.3.0
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: i18n
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id004
|
82
|
+
description: Rails i18n locale web editor as plugin
|
83
|
+
email: adam.kliment@virtualmaster.cz
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE
|
90
|
+
- README.rdoc
|
91
|
+
files:
|
92
|
+
- .document
|
93
|
+
- .gitignore
|
94
|
+
- LICENSE
|
95
|
+
- README.rdoc
|
96
|
+
- Rakefile
|
97
|
+
- VERSION
|
98
|
+
- app/controllers/translations_controller.rb
|
99
|
+
- app/helpers/translations_helper.rb
|
100
|
+
- app/models/translation.rb
|
101
|
+
- app/views/layouts/translations.erb
|
102
|
+
- app/views/translations/index.html.erb
|
103
|
+
- config/routes.rb
|
104
|
+
- lib/hash_tools.rb
|
105
|
+
- lib/i18n_locale_editor.rb
|
106
|
+
- lib/translate_monkey_patch.rb
|
107
|
+
- public/i18n-locale-editor.js
|
108
|
+
- rails/init.rb
|
109
|
+
- spec/i18n_locale_editor_spec.rb
|
110
|
+
- spec/spec.opts
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/n1k/i18n_locale_editor
|
114
|
+
licenses: []
|
115
|
+
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options:
|
118
|
+
- --charset=UTF-8
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
requirements: []
|
140
|
+
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.3.7
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: Rails i18n locale editor
|
146
|
+
test_files:
|
147
|
+
- spec/i18n_locale_editor_spec.rb
|
148
|
+
- spec/spec_helper.rb
|