5 |
6 |
7 | <%= select_tag(:from_locale, options_for_select(I18n.available_locales, @from_locale.to_sym)) %> to
8 | <%= select_tag(:to_locale, options_for_select(I18n.available_locales, @to_locale.to_sym)) %>
9 | <%= submit_tag "Display" %>
10 |
11 | <%= link_to '.. /', '../' %>
12 | <%= link_to @page_title, root_path %>
13 |
14 | <%= simple_filter(@show_filters).html_safe %>
15 | Found <%= @total_entries %> messages
16 | <%= link_to "Reload messages", translate_reload_path %>
17 | <%# link_to "Reload messages", translate_reload_path(params) %>
18 |
19 |
20 | <% flash.each do |key, msg| %>
21 |
<%= msg %>
22 | <% end %>
23 |
24 |
25 | <%= text_field_tag(:per_page, session[:per_page], size: 2) %>
26 | Per page
27 | <%= select_tag(:key_type, options_for_select([["Key contains", 'contains'], ["Key starts with", 'starts_with']], params[:key_type])) %>
28 | <%= text_field_tag(:key_pattern, params[:key_pattern], :size => 50, :id => "key_pattern_value", :class => "text-default") %>
29 |
30 | <%= select_tag(:text_type, options_for_select([['Text contains','contains'], ['Text equals','equals']], params[:text_type])) %>
31 | <%= text_field_tag(:text_pattern, params[:text_pattern], :size => 50, :id => "text_pattern_value", :class => "text-default") %>
32 |
33 | <%= link_to "clear", @filter_params.to_h.merge({:text_pattern => nil, :key_pattern => nil}), class: 'btn' %>
34 | <%= submit_tag "Search" %>
35 | <%= link_to "Export language file", translate_export_path(:locale => @to_locale), class: 'btn sep' %>
36 |
37 | <% end %>
38 |
39 |
44 |
45 | <%= render :partial => 'pagination', :locals => { :total_entries => @total_entries, :per_page => @per_page } %>
46 |
47 |
48 | <% if @total_entries > 0 %>
49 | <%= form_tag(translate_path, method: :put) do %>
50 | <%= hidden_field_tag(:filter, params[:filter], :id => "hid_filter") %>
51 | <%= hidden_field_tag(:sort_by, params[:sort_by], :id => "hid_sort_by") %>
52 | <%= hidden_field_tag(:key_type, params[:key_type], :id => "hid_key_type") %>
53 | <%= hidden_field_tag(:key_pattern, params[:key_pattern], :id => "hid_key_pattern") %>
54 | <%= hidden_field_tag(:text_type, params[:text_type], :id => "hid_text_type") %>
55 | <%= hidden_field_tag(:text_pattern, params[:text_pattern], :id => "hid_text_pattern") %>
56 |
57 |
58 | Translations from <%= @from_locale %> to <%= @to_locale %> -
59 | <%= simple_filter(["key", "text"], 'sort_by').html_safe %>
60 | <%= submit_tag "Save Translations", style: 'float:right;' %>
61 |
62 | <% @keys.translations.each do |tr| %>
63 |
64 |
65 |
66 | <% if tr.files %>
67 |
68 |
69 | found in <%= tr.files.count %> files
70 |
71 |
72 |
<%= tr.files.join("
").html_safe %>
73 |
74 |
75 | <% end %>
76 |
77 | <%= h tr.key %>
78 | <% if tr.persisted %>
79 | (saved)
80 | <% else %>
81 | (unsaved)
82 | <% end %>
83 |
84 |
X
85 |
86 | <% if tr.lines > 1 %>
87 |
88 |
89 |
<%= tr.from_text %>
90 |
91 |
92 |
93 | <%= text_area_tag("key[#{tr.key}]", tr.to_text, :rows => tr.lines, :id => tr.id) %>
94 |
95 |
96 |
97 |
98 | <% else %>
99 |
100 | <%= tr.from_text %>
101 |
102 |
▶
103 | <%= text_field_tag("key[#{tr.key}]", tr.to_text, :size => tr.lines, :id => tr.id) %>
104 | <% end %>
105 |
106 |
107 | <% end %>
108 |
109 | Translations from <%= @from_locale %> to <%= @to_locale %>
110 | <%= submit_tag "Save Translations", style: 'float:right' %>
111 |
112 |
113 | <% end %>
114 | <% end %>
115 |
116 |
117 | <%= render :partial => 'pagination', :locals => { :total_entries => @total_entries, :per_page => @per_page } %>
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/spec/lib/keys_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'spec_helper'
4 |
5 | describe RailsI18nterface::Keys do
6 | include RailsI18nterface::Utils
7 |
8 | let(:root_dir) { File.expand_path(File.join('..', '..', '..', 'spec', 'internal'), __FILE__) }
9 | let(:keys) { RailsI18nterface::Keys.new(root_dir, :sv, :no) }
10 |
11 | describe '.files' do
12 | let(:expected) { [
13 | 'activerecord.attributes.article.active',
14 | 'activerecord.attributes.article.body',
15 | 'activerecord.attributes.article.created_at',
16 | 'activerecord.attributes.article.title',
17 | 'activerecord.attributes.article.updated_at',
18 | 'activerecord.attributes.topic.created_at',
19 | 'activerecord.attributes.topic.title',
20 | 'activerecord.attributes.topic.updated_at',
21 | 'activerecord.models.article',
22 | 'activerecord.models.topic',
23 | 'article.key1',
24 | 'article.key2',
25 | 'article.key3',
26 | 'article.key4.one',
27 | 'article.key4.other',
28 | 'article.key4.zero',
29 | 'article.key5',
30 | 'article.key6.one',
31 | 'article.key6.other',
32 | 'article.key6.zero',
33 | 'category_html_erb.key1',
34 | 'category_rhtml.key1',
35 | 'js.alert',
36 | 'symbol_key',
37 | 'title'
38 | ] }
39 | it 'extracts keys from I18n lookups in .rb, .html.erb, and .rhtml files' do
40 | expect(keys.files.keys.sort).to eq expected
41 | end
42 | end
43 |
44 | describe "sorts by keys" do
45 | #before { allow(keys).to receive(:lookup).with('en', 'article.key6.zero').and_return('xxx') }
46 | it { expect(keys.sort_keys.last).to eq 'title' }
47 | end
48 |
49 | describe "sorts by translation" do
50 | it { expect(keys.sort_keys('text').last).to eq 'time.pm' }
51 | end
52 |
53 | describe 'return a hash with I18n keys and file lists' do
54 | it { expect(keys.files[:'article.key3']).to eq ['app/models/article.rb'] }
55 | end
56 |
57 | describe 'reloads the translatable strings' do
58 | let(:size) { keys.all_keys.size }
59 | it { expect(keys.reload(root_dir).size).to be size }
60 | end
61 |
62 | describe 'i18n_keys' do
63 | before do
64 | I18n.backend.send(:init_translations) unless I18n.backend.initialized?
65 | end
66 |
67 | describe 'should return all keys in the I18n backend translations hash' do
68 | before { allow(I18n.backend).to receive(:translations).and_return(translations) }
69 | it { expect(keys.i18n_keys :en).to eq ['articles.new.page_title', 'categories.flash.created', 'empty', 'home.about'] }
70 | end
71 |
72 | describe 'untranslated_keys' do
73 | let(:expected) {
74 | {
75 | sv: ['articles.new.page_title', 'categories.flash.created', 'empty'],
76 | no: ['articles.new.page_title', 'categories.flash.created', 'empty', 'home.about']
77 | }
78 | }
79 | before { allow(I18n.backend).to receive(:translations).and_return(translations) }
80 |
81 | it 'should return a hash with keys with missing translations in each locale' do
82 | expect(keys.untranslated_keys).to eq expected
83 | end
84 | end
85 |
86 | describe 'missing_keys' do
87 | let(:file_path) { File.join(root_dir, 'config', 'locales', 'en.yml') }
88 | before do
89 | yaml = RailsI18nterface::Yamlfile.new(root_dir, :en)
90 | yaml.write({
91 | en: {
92 | home: {
93 | page_title: false,
94 | intro: {
95 | one: 'intro one',
96 | other: 'intro other'
97 | }
98 | }
99 | }
100 | })
101 | allow(keys).to receive(:files).and_return({
102 | :'home.page_title' => 'app/views/home/index.rhtml',
103 | :'home.intro' => 'app/views/home/index.rhtml',
104 | :'home.signup' => 'app/views/home/_signup.rhtml',
105 | :'about.index.page_title' => 'app/views/about/index.rhtml'
106 | })
107 | end
108 |
109 | after(:each) do
110 | FileUtils.rm(file_path) if File.exists? file_path
111 | end
112 |
113 | it 'should return a hash with keys that are not in the locale file' do
114 | expect(keys.missing_keys.sort[0]).to eq "activerecord.attributes.article.active"
115 | end
116 | end
117 |
118 |
119 | describe 'translated_locales' do
120 | before do
121 | allow(I18n).to receive(:default_locale).and_return(:en)
122 | allow(I18n).to receive(:available_locales).and_return([:sv, :no, :en, :root])
123 | end
124 |
125 | it 'returns all avaiable except :root and the default' do
126 | expect(RailsI18nterface::Keys.translated_locales).to eq [:sv, :no]
127 | end
128 | end
129 |
130 |
131 | ##########################################################################
132 | #
133 | # Helper Methods
134 | #
135 | ##########################################################################
136 |
137 | def translations
138 | {
139 | en: {
140 | home: {
141 | about: 'This site is about making money'
142 | },
143 | articles: {
144 | new: {
145 | page_title: 'New Article'
146 | }
147 | },
148 | categories: {
149 | flash: {
150 | created: 'Category created'
151 | }
152 | },
153 | empty: nil
154 | },
155 | sv: {
156 | home: {
157 | about: false
158 | }
159 | }
160 | }
161 | end
162 | end
163 |
164 | def shallow_hash
165 | {
166 | 'pressrelease.label.one' => 'Pressmeddelande',
167 | 'pressrelease.label.other' => 'Pressmeddelanden',
168 | 'article' => 'Artikel',
169 | 'category' => ''
170 | }
171 | end
172 |
173 | def deep_hash
174 | {
175 | pressrelease: {
176 | label: {
177 | one: 'Pressmeddelande',
178 | other: 'Pressmeddelanden'
179 | }
180 | },
181 | article: 'Artikel',
182 | category: ''
183 | }
184 | end
185 |
186 | end
187 |
--------------------------------------------------------------------------------
/spec/controllers/translate_controller_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'spec_helper'
4 |
5 | describe RailsI18nterface::TranslateController, :type => :controller do
6 |
7 | let(:root_dir) { File.expand_path(File.join('..', '..', '..', 'spec', 'internal'), __FILE__) }
8 | let(:yaml_trad) { File.join(root_dir, 'config', 'locales', 'en.yml') }
9 |
10 | before { @routes = RailsI18nterface::Engine.routes }
11 | after { FileUtils.rm yaml_trad if File.exists? yaml_trad }
12 |
13 | describe '.update' do
14 | let(:key_param) { {} }
15 | # hmm, this is called 7 times, I would like to know why
16 | # I18n.backend.should_receive(:store_translations)
17 | before {
18 | session[:from_locale] = :sv
19 | session[:to_locale] = :en
20 | allow(RailsI18nterface::Yamlfile).to receive(:write_to_file)
21 | }
22 | it 'stores translations to I18n backend and then write them to a YAML file' do
23 | put :update, params: { key: key_param }
24 | expect(response).to be_redirect
25 | end
26 | end
27 |
28 | # TODO: improve this test
29 | it 'exports the yml file from current translations' do
30 | get :export, params: { locale: 'en' }
31 | expect(response.headers['Content-Disposition']).to eq "attachment; filename=en.yml"
32 | end
33 |
34 | # TODO: improve this test
35 | it 'reloads the string to translate from changed string in source code' do
36 | get :reload
37 | expect(response).to be_redirect
38 | end
39 |
40 | # TODO make this test a real one
41 | describe 'delete' do
42 | let(:tempfile) { File.join(root_dir, 'app', 'views', 'categories', 'category.erb') }
43 | it 'remove key on demand' do
44 | post :destroy, params: { del: 'article.key6.other' }
45 | expect(response).to be_redirect
46 | # get_page :index, per_page: 1, key_pattern: 'article.key6.other', key_type: 'starts_with'
47 | # expect(assigns :total_entries).to eq 0
48 | end
49 | end
50 |
51 | describe 'index' do
52 |
53 | include RailsI18nterface::Utils
54 |
55 | before do
56 | allow(I18n.backend).to receive(:translations).and_return(i18n_translations)
57 | I18n.backend.instance_eval { @initialized = true }
58 | allow(I18n).to receive(:valid_locales).and_return([:en, :sv])
59 | allow(I18n).to receive(:default_locale).and_return(:sv)
60 | end
61 |
62 | it 'can switch languages' do
63 | get_page :index, params: { per_page: 1, from_locale: 'sv', to_locale: 'en' }
64 | expect(assigns :from_locale).to be :sv
65 | expect(assigns :to_locale).to be :en
66 | end
67 |
68 | it 'shows sorted paginated keys from the translate from locale and extracted keys by default' do
69 | get_page :index, params: { per_page: 1 }
70 | expect(assigns :from_locale).to be :sv
71 | expect(assigns :to_locale).to be :sv
72 | expect(assigns(:keys).all_keys.sort[0]).to eq 'activerecord.attributes.article.active'
73 | expect(assigns(:keys).translations.map(&:key)).to eq ['activerecord.attributes.article.active']
74 | end
75 |
76 | it 'can be paginated with the page param' do
77 | get_page :index, params: { per_page: 1, page: 2 }
78 | expect(assigns(:keys).translations.map(&:key)).to eq ['activerecord.attributes.article.body']
79 | end
80 |
81 | it 'has a default sort order by key' do
82 | get_page :index, params: { per_page: 1 }
83 | expect(controller.params[:sort_by]).to eq 'key'
84 | end
85 |
86 | it 'can sort by key' do
87 | get_page :index, params: { per_page: 1, filter: 'translated', sort_by: 'key' }
88 | expect(assigns(:keys).translations.map(&:key)).to eq ['articles.new.page_title']
89 | end
90 |
91 | it 'can sort by text' do
92 | get_page :index, params: { per_page: 1, filter: 'translated', sort_by: 'text' }
93 | expect(assigns(:keys).translations.map(&:key)).to eq ['vendor.foobar']
94 | end
95 |
96 | it 'can filter to see only the root items when using . as pattern' do
97 | get_page :index, params: { per_page: 2, key_pattern: '.', key_type: 'starts_with' }
98 | expect(assigns :total_entries).to be 2
99 | expect(assigns(:keys).translations.map(&:key)).to eq ['symbol_key','title']
100 | end
101 |
102 | it 'accepts a key_pattern param with key_type=starts_with' do
103 | get_page :index, params: { per_page: 1, key_pattern: 'articles', key_type: 'starts_with' }
104 | expect(assigns :total_entries).to be 1
105 | expect(assigns(:keys).translations.map(&:key)).to eq ['articles.new.page_title']
106 | end
107 |
108 | it 'accepts a key_pattern param with key_type=contains' do
109 | get_page :index, params: { per_page: 1, key_pattern: 'page_', key_type: 'contains' }
110 | expect(assigns :total_entries).to be 2
111 | expect(assigns(:keys).translations.map(&:key)).to eq ['articles.new.page_title']
112 | end
113 |
114 | it 'accepts a filter=untranslated param' do
115 | get_page :index, params: { per_page: 1, filter: 'untranslated' }
116 | expect(assigns(:keys).all_keys.sort[0]).to eq 'activerecord.attributes.article.active'
117 | expect(assigns(:keys).translations.map(&:key)).to eq ['activerecord.attributes.article.active']
118 | end
119 |
120 | it 'accepts a filter=translated param' do
121 | get_page :index, params: { per_page: 1, filter: 'translated' }
122 | expect(assigns :total_entries).to be 3
123 | expect(assigns(:keys).translations.map(&:key)).to eq ['articles.new.page_title']
124 | end
125 |
126 | def i18n_translations
127 | HashWithIndifferentAccess.new({
128 | :en => {
129 | :vendor => {
130 | :foobar => 'Foo Baar'
131 | }
132 | },
133 | :sv => {
134 | :articles => {
135 | :new => {
136 | :page_title => 'Skapa ny artikel'
137 | }
138 | },
139 | :home => {
140 | :page_title => 'Valkommen till I18n'
141 | },
142 | :vendor => {
143 | :foobar => 'Fobar'
144 | }
145 | }
146 | })
147 | end
148 |
149 | def files
150 | HashWithIndifferentAccess.new({
151 | :'home.page_title' => ['app/views/home/index.rhtml'],
152 | :'general.back' => ['app/views/articles/new.rhtml', 'app/views/categories/new.rhtml'],
153 | :'articles.new.page_title' => ['app/views/articles/new.rhtml']
154 | })
155 | end
156 | end
157 |
158 | def get_page(*args)
159 | get(*args)
160 | expect(response).to be_success
161 | end
162 |
163 | end
164 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/rails_i18nterface/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory
3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4 | * the top of the compiled file, but it's generally better to create a new file per style scope.
5 | *= require_self
6 | *= require_tree .
7 | */
8 | /*reset.css*/
9 | /* v1.0 | 20080212 */
10 | html, body, div, span, applet, object, iframe,
11 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
12 | a, abbr, acronym, address, big, cite, code,
13 | del, dfn, em, font, img, ins, kbd, q, s, samp,
14 | small, strike, strong, sub, sup, tt, var,
15 | b, u, i, center,
16 | dl, dt, dd, ol, ul, li,
17 | fieldset, form, label, legend,
18 | table, caption, tbody, tfoot, thead, tr, th, td {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | outline: 0;
23 | font-size: 100%;
24 | vertical-align: baseline;
25 | background: transparent;
26 | }
27 | body {
28 | line-height: 1;
29 | }
30 | ol, ul {
31 | list-style: none;
32 | }
33 | blockquote, q {
34 | quotes: none;
35 | }
36 | blockquote:before, blockquote:after,
37 | q:before, q:after {
38 | content: '';
39 | content: none;
40 | }
41 | pre, code {
42 | font-size:1.1em;
43 | }
44 | /* remember to define focus styles! */
45 | :focus {
46 | outline: 0;
47 | }
48 |
49 | /* remember to highlight inserts somehow! */
50 | ins {
51 | text-decoration: none;
52 | }
53 | del {
54 | text-decoration: line-through;
55 | }
56 |
57 | /* tables still need 'cellspacing="0"' in the markup */
58 | table {
59 | border-collapse: collapse;
60 | border-spacing: 0;
61 | }
62 | /*clear fix*/
63 | .clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden;}
64 | .clearfix{display:inline-block;}
65 | html[xmlns] .clearfix {
66 | display: block;
67 | }
68 | * html .clearfix{height:1%;}
69 | /*start layout*/
70 | body{
71 | background:#fff;
72 | color:#333;
73 | font-size:75%;
74 | font-family:Arial;
75 | margin:0;
76 | line-height:1.5em;
77 | }
78 | textarea,input,select{
79 | font-family:Arial;
80 | font-size:1em;
81 | }
82 | h1{
83 | color:#9ac;
84 | font-size:2em;
85 | margin-bottom:0.5em;
86 | }
87 | h2{
88 | text-align:left;
89 | color:#d46021;
90 | font-size:1.3em;
91 | padding-left:0;
92 | }
93 | a{
94 | color:#258;
95 | }
96 | * {
97 | box-sizing: border-box;
98 | -moz-box-sizing: border-box;
99 | -webkit-box-sizing: border-box;
100 | }
101 | /* master layout */
102 | #top {
103 | position: fixed;
104 | height: 40px;
105 | width: 100%;
106 | }
107 | #searchbox {
108 | position: fixed;
109 | top: 35px;
110 | width: 100%;
111 | height: 30px;
112 | padding: 5px 20px;
113 | background-color: #ccc;
114 | border-bottom: 2px dotted #999;
115 | }
116 | #namespaces {
117 | position: fixed;
118 | top: 65px;
119 | width: 200px;
120 | height: calc(100% - 65px);
121 | overflow-y: scroll;
122 | background-color: #ddd;
123 | padding-top: 10px;
124 | box-shadow: inset -5px 4px 8px #aaa;
125 | }
126 | #inside {
127 | position: fixed;
128 | top: 65px;
129 | left: 200px;
130 | bottom: 0;
131 | right: 0;
132 | overflow-y: auto;
133 | }
134 |
135 |
136 | /* inside layout */
137 | div#container{
138 | width:100%;
139 | margin:0 auto;
140 | font-size:1em;
141 | }
142 | div#container h1 {
143 | padding: 5px 20px;
144 | background-color: #000;
145 | margin: 0;
146 | height: 35px;
147 | line-height: 30px;
148 | vertical-align: middle;
149 | }
150 | div#container h1 a {
151 | color: #8ac;
152 | text-decoration: none;
153 | }
154 | div#container h1 a:hover {
155 | color: #fff;
156 | }
157 | div#container h1 .right {
158 | font-size: 12px;
159 | display: inline-block;
160 | float: right;
161 | margin-left: 20px;
162 | }
163 | div#container h1 .center {
164 | color: #fff;
165 | font-size: 12px;
166 | font-weight: normal;
167 | display: inline-block;
168 | float: right;
169 | }
170 | div#container #success {
171 | padding: 5px 20px;
172 | background-color: #ada;
173 | }
174 | div#container #error {
175 | padding: 5px 20px;
176 | background-color: #daa;
177 | }
178 | div#container #notice {
179 | padding: 5px 20px;
180 | background-color: #dba;
181 | }
182 | /* menu namespaces */
183 | #namespaces ul {
184 | border-top: 1px dotted #aaa;
185 | border-bottom: 1px dotted #aaa;
186 | margin-bottom: 1px;
187 | font-weight: normal;
188 | }
189 | #namespaces .num {
190 | font-size: 10px;
191 | color: #999;
192 | }
193 | #namespaces ul .display {
194 | width: 42px;
195 | height: 10px;
196 | margin-top: 3px;
197 | float: right;
198 | position: absolute;
199 | right: 0;
200 | }
201 | #namespaces ul .display:hover {
202 | background-color: #fff;
203 | background: -moz-linear-gradient(0% 0% 0deg, rgba(255,255,255,0), rgba(255,255,255,1) 100%);
204 | background: -ms-linear-gradient(0% 0% 0deg, rgba(255,255,255,0), rgba(255,255,255,1) 100%);
205 | background: -webkit-gradient(linear, 0% 0%, 100% 0%, from(rgba(255,255,255,0)), to(rgba(255,255,255,1)));
206 | background: gradient(0% 0% 0deg, rgba(255,255,255,0), rgba(255,255,255,1) 100%);
207 | }
208 | #namespaces ul .display:hover:before {
209 | content: '';
210 | width: 0;
211 | height: 0;
212 | border: 5px solid transparent;
213 | border-left: 5px solid #369;
214 | position: absolute;
215 | right: 0px;
216 | z-index: 1;
217 | }
218 | #namespaces > ul > li {
219 | font-weight: bold;
220 | }
221 | #namespaces ul li {
222 | padding-left: 12px;
223 | cursor: pointer;
224 | position: relative;
225 | }
226 | #namespaces ul li.item {
227 | font-style: italic;
228 | }
229 | #namespaces ul li:hover {
230 | background-color: #cfcfcf;
231 | }
232 | #namespaces ul li ul li:hover {
233 | background-color: #c4c4c4;
234 | }
235 | #namespaces ul li ul li ul li:hover {
236 | background-color: #b8b8b8;
237 | }
238 | #namespaces ul li ul li ul li ul li:hover {
239 | background-color: #b0b0b0;
240 | }
241 | #namespaces ul li ul li ul li ul li ul li:hover {
242 | background-color: #a4a4a4;
243 | }
244 | #namespaces ul li.item:before {
245 | position: absolute;
246 | content: "- ";
247 | margin-left: -9px;
248 | color: #777;
249 | }
250 | #namespaces ul li.dir:before {
251 | position: absolute;
252 | content: "";
253 | width: 5px;
254 | height: 0;
255 | border: 5px solid transparent;
256 | border-left: 5px solid #999;
257 | margin-left: -9px;
258 | margin-top: 4px;
259 | }
260 | #namespaces ul li ul {
261 | display: none;
262 | }
263 | #namespaces ul li ul.view {
264 | display: block;
265 | }
266 | /* paging */
267 | div.paging {
268 | text-align: left;
269 | }
270 | div.paging div {
271 | padding: 2px 20px;
272 | border: solid 1px #d5d6d5;
273 | border-left: 0;
274 | background: #f1f1f1;
275 | border-radius: 0 15px 15px 0;
276 | }
277 | ul.paging {
278 | display: block;
279 | margin: 0;
280 | }
281 | ul.paging li {
282 | display: block;
283 | margin: 0;
284 | float: left;
285 | }
286 | ul.paging li.selected a {
287 | color: #fff;
288 | background: #258;
289 | font-weight: bold;
290 | width: 20px;
291 | padding: 7px 10px;
292 | text-decoration: none;
293 | border-radius: 5px;
294 | }
295 | ul.paging li a {
296 | line-height: 1em;
297 | padding: 0.5em 0.5em;
298 | min-width: 20px;
299 | text-decoration: none;
300 | font-weight: bold;
301 | padding: 4px 10px;
302 | }
303 | ul.paging li a:hover {
304 | background: #ccc;
305 | }
306 | ul.paging li.selected a:hover {
307 | background: #000;
308 | }
309 | li.gap {
310 | padding: 0 1em;
311 | letter-spacing: .5em;
312 | }
313 | /*forms filter*/
314 | #searchbox .sep {
315 | margin-left: 40px;
316 | }
317 | div#searchbox select{
318 | margin-left:1.5em;
319 | }
320 | div#searchbox input.text-default{
321 | width:100px;
322 | }
323 | .btn {
324 | text-decoration: none;
325 | font-weight: bold;
326 | color: #444;
327 | padding: 2px 10px;
328 | border: 1px solid #666;
329 | border-radius: 4px;
330 | background-color: #eee;
331 | line-height: 16px;
332 | }
333 | .btn:hover {
334 | background: #ccc;
335 | color: #258;
336 | }
337 | .btn:active {
338 | }
339 | input[type=submit] {
340 | cursor: pointer;
341 | font-weight: bold;
342 | font-weight: bold;
343 | color: #444;
344 | padding: 1px 10px;
345 | border: 1px solid #666;
346 | line-height: 16px;
347 | border-radius: 4px;
348 | background-color: #eee;
349 | }
350 | input[type=submit]:hover {
351 | background: #ccc;
352 | color: #258;
353 | }
354 | input[type=submit]:active {
355 | }
356 | /*translation edit*/
357 | div.translations {
358 | }
359 | p.translate {
360 | border-top: solid 1px #d5d6d5;
361 | border-bottom: solid 1px #d5d6d5;
362 | background: #f1f1f1;
363 | padding: 4px 20px;
364 | }
365 | div.translation {
366 | padding: 6px 20px;
367 | margin-bottom: 6px;
368 | border-bottom: solid 1px #d5d6d5;
369 | clear:both;
370 | }
371 | div.translation .change {
372 | position: absolute;
373 | display: inline-block;
374 | margin-left: -15px;
375 | cursor: pointer;
376 | color: #c3c6c9;
377 | text-decoration: none;
378 | }
379 | div.translation .change:hover {
380 | color: #369;
381 | }
382 | div.translation input, div.translation textarea {
383 | font-family: monospace;
384 | font-size: 1.1em;
385 | width: 100%;
386 | display: inline-block;
387 | padding: 4px;
388 | border: 1px solid #aaa;
389 | border-radius: 3px;
390 | position: relative;
391 | }
392 | div.translation textarea {
393 | }
394 | div.translation em strong {
395 | color:#333;
396 | padding-right:0.5em;
397 | }
398 | .translation .right {
399 | float: right;
400 | }
401 | .translation .files {
402 | padding: 1px 5px;
403 | background-color: #369;
404 | color: #fff;
405 | border-radius: 3px 3px 0 0;
406 | float: right;
407 | display: inline;
408 | margin-left: 10px;
409 | position: relative;
410 | cursor: pointer;
411 | }
412 |
413 | .translation .files .count {
414 | display: inline-block;
415 |
416 | }
417 | .translation .files .filelist {
418 | display: none;
419 | position: absolute;
420 | top: 15px;
421 | right: 0;
422 | left: auto;
423 | width: 300px;
424 | background-color: #fff;
425 | padding: 5px 10px;
426 | border: 1px solid #369;
427 | z-index: 1;
428 | color: #000;
429 | }
430 | .translation .files:hover .filelist {
431 | display: block;
432 | }
433 | .translation .key {
434 | color: #666;
435 | }
436 | .translation .delete {
437 | padding: 0 5px;
438 | text-decoration: none;
439 | background-color: #999;
440 | color: #fff;
441 | border-radius: 10px;
442 | font-size: 10px;
443 | font-weight: bold;
444 | }
445 | .translation .delete:hover {
446 | background-color: #900;
447 | }
448 | .translation .keytext {
449 | padding: 5px;
450 | color: #678;
451 | }
452 | div.translation a{
453 | }
454 | div.translation input.btnDefault{
455 | margin:0 0 1em;
456 | width:auto;
457 | }
458 | .focus-text{
459 | }
460 | div.selected{
461 | }
462 | .display{
463 | display:block !important;
464 | }
465 | /*feedback*/
466 | .long-translation {
467 | clear: both;
468 | }
469 | .long-translation .translation-text {
470 | float: left;
471 | width: 50%;
472 | padding: 4px;
473 | border: 1px solid #eee;
474 | border-radius: 3px;
475 | }
476 | .long-translation .translation-text pre {
477 | white-space: normal;
478 | }
479 | .long-translation .translation-textarea {
480 | float: left;
481 | width: 50%;
482 | }
483 | .long-translation .translation-text,
484 | .long-translation .translation-textarea textarea {
485 | line-height: 175%;
486 | font-family: monospace;
487 | }
488 |
489 | .translation input,
490 | .long-translation .translation-textarea textarea {
491 | background-color: #f3f6f9;
492 | }
493 |
494 | .flash {
495 | text-align: right;
496 | }
497 | .clear {
498 | clear: both;
499 | }
500 |
501 |
--------------------------------------------------------------------------------
/app/assets/javascripts/rails_i18nterface/ender.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * =============================================================
3 | * Ender: open module JavaScript framework (https://ender.no.de)
4 | * Build: ender build jeesh reqwest --output app/assets/javascripts/rails_i18nterface/ender
5 | * =============================================================
6 | */
7 |
8 |
9 | /*!
10 | * Ender: open module JavaScript framework (client-lib)
11 | * copyright Dustin Diaz & Jacob Thornton 2011-2012 (@ded @fat)
12 | * http://ender.jit.su
13 | * License MIT
14 | */
15 | (function(context){function require(identifier){var module=modules["$"+identifier]||window[identifier];if(!module)throw new Error("Ender Error: Requested module '"+identifier+"' has not been defined.");return module}function provide(name,what){return modules["$"+name]=what}function aug(o,o2){for(var k in o2)k!="noConflict"&&k!="_VERSION"&&(o[k]=o2[k]);return o}function Ender(s,r){var elements,i;this.selector=s;if(typeof s=="undefined"){elements=[];this.selector=""}else typeof s=="string"||s.nodeName||s.length&&"item"in s||s==window?elements=ender._select(s,r):elements=isFinite(s.length)?s:[s];this.length=elements.length;for(i=this.length;i--;)this[i]=elements[i]}function ender(s,r){return new Ender(s,r)}context.global=context;var modules={},old=context.$,oldEnder=context.ender,oldRequire=context.require,oldProvide=context.provide;context.provide=provide;context.require=require;Ender.prototype.forEach=function(fn,opt_scope){var i,l;for(i=0,l=this.length;i