translate-rails3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +62 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/app/controllers/translate_controller.rb +165 -0
- data/app/helpers/translate_helper.rb +45 -0
- data/app/views/layouts/translate.html.erb +359 -0
- data/app/views/translate/_pagination.html.erb +24 -0
- data/app/views/translate/index.html.erb +114 -0
- data/config/routes.rb +5 -0
- data/init.rb +1 -0
- data/lib/tasks/translate.rake +178 -0
- data/lib/translate.rb +8 -0
- data/lib/translate/file.rb +35 -0
- data/lib/translate/keys.rb +152 -0
- data/lib/translate/log.rb +35 -0
- data/lib/translate/routes.rb +11 -0
- data/lib/translate/storage.rb +28 -0
- data/spec/controllers/translate_controller_spec.rb +130 -0
- data/spec/file_spec.rb +54 -0
- data/spec/files/translate/app/models/article.rb +12 -0
- data/spec/files/translate/app/views/category.erb +1 -0
- data/spec/files/translate/app/views/category.html +1 -0
- data/spec/files/translate/app/views/category.html.erb +1 -0
- data/spec/files/translate/app/views/category.rhtml +5 -0
- data/spec/files/translate/public/javascripts/application.js +1 -0
- data/spec/keys_spec.rb +179 -0
- data/spec/log_spec.rb +47 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/storage_spec.rb +33 -0
- data/translate-rails3.gemspec +77 -0
- metadata +118 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
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
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Translate
|
2
|
+
=========
|
3
|
+
|
4
|
+
This plugin provides a web interface for translating Rails I18n texts (requires Rails 3.0 or higher) from one locale to another. The plugin has been tested only with the simple I18n backend that ships with Rails. I18n texts are read from and written to YAML files under config/locales.
|
5
|
+
|
6
|
+
To translate to a new locale you need to add a YAML file for that locale that contains the locale as the top key and at least one translation.
|
7
|
+
|
8
|
+
Please note that there are certain I18n keys that map to Array objects rather than strings and those are currently not dealt with by the translation UI. This means that Rails built in keys such as date.day_names need to be translated manually directly in the YAML file.
|
9
|
+
|
10
|
+
To get the translation UI to write the YAML files in UTF8 you need to install the ya2yaml gem.
|
11
|
+
|
12
|
+
The translation UI finds all I18n keys by extracting them from I18n lookups in your application source code. In addition it adds all :en and default locale keys from the I18n backend.
|
13
|
+
|
14
|
+
- Updated: Each string in the UI now has an "Auto Translate" link which will send the original text to Google Translate and will input the returned translation into the form field for further clean up and review prior to saving.
|
15
|
+
|
16
|
+
|
17
|
+
Rake Tasks
|
18
|
+
=========
|
19
|
+
|
20
|
+
In addition to the web UI this plugin adds the following rake tasks:
|
21
|
+
|
22
|
+
translate:untranslated
|
23
|
+
translate:missing
|
24
|
+
translate:remove_obsolete_keys
|
25
|
+
translate:merge_keys
|
26
|
+
translate:google
|
27
|
+
translate:changed
|
28
|
+
|
29
|
+
The missing task shows you any I18n keys in your code that do not have translations in the YAML file for your default locale, i.e. config/locales/sv.yml.
|
30
|
+
|
31
|
+
The merge_keys task is supposed to be used in conjunction with Sven Fuch's Rails I18n TextMate bundle (http://github.com/svenfuchs/rails-i18n/tree/master). Texts and keys extracted with the TextMate bundle end up in the temporary file log/translations.yml. When you run the merge_keys rake task the keys are moved over to the corresponding I18n locale file, i.e. config/locales/sv.yml. The merge_keys task also checks for overwrites of existing keys by warning you that one of your extracted keys already exists with a different translation.
|
32
|
+
|
33
|
+
The google task is used for auto translating from one locale to another using Google Translate.
|
34
|
+
|
35
|
+
The changed rake task can show you between one YAML file to another which keys have had their texts changed.
|
36
|
+
|
37
|
+
Installation
|
38
|
+
=========
|
39
|
+
|
40
|
+
Add to your Gemfile:
|
41
|
+
|
42
|
+
gem 'translate-rails3', :require => 'translate', :group => :development
|
43
|
+
|
44
|
+
Now visit /translate in your web browser to start translating.
|
45
|
+
|
46
|
+
Dependencies
|
47
|
+
=========
|
48
|
+
|
49
|
+
- Rails 2.2 or higher
|
50
|
+
- The ya2yaml gem if you want your YAML files written in UTF8 encoding.
|
51
|
+
|
52
|
+
Authors
|
53
|
+
=========
|
54
|
+
|
55
|
+
- Peter Marklund (programming)
|
56
|
+
- Joakim Westerlund (web design)
|
57
|
+
- Milan Novota (initial Rails 3 support)
|
58
|
+
- Roman Shterenzon (Rails 3 cleanup and gem packaging)
|
59
|
+
|
60
|
+
Many thanks to http://newsdesk.se for sponsoring the development of this plugin.
|
61
|
+
|
62
|
+
Copyright (c) 2009 Peter Marklund, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
|
3
|
+
desc 'Default: run specs.'
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
desc 'Run the specs'
|
7
|
+
RSpec::Core::RakeTask.new do |t|
|
8
|
+
t.rspec_opts = ['--color --format progress']
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
gem.name = 'translate-rails3'
|
15
|
+
gem.summary = %Q{Newsdesk translate plugin for Rails 3}
|
16
|
+
gem.description = <<EOF
|
17
|
+
This plugin provides a web interface for translating Rails I18n texts
|
18
|
+
(requires Rails 3.0 or higher) from one locale to another.
|
19
|
+
The plugin has been tested only with the simple I18n backend that ships
|
20
|
+
with Rails.
|
21
|
+
I18n texts are read from and written to YAML files under config/locales.
|
22
|
+
|
23
|
+
This gem is a fork of the original https://github.com/mynewsdesk/translate
|
24
|
+
and also includes work from this fork: https://github.com/milann/translate
|
25
|
+
EOF
|
26
|
+
gem.email = 'romanbsd@yahoo.com'
|
27
|
+
gem.homepage = 'https://github.com/romanbsd/translate'
|
28
|
+
gem.authors = ['Peter Marklund', 'Milan Novota', 'Roman Shterenzon']
|
29
|
+
gem.add_dependency 'ya2yaml', '~> 0.30' # For UTF-8 support in YAML
|
30
|
+
end
|
31
|
+
Jeweler::GemcutterTasks.new
|
32
|
+
rescue LoadError
|
33
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,165 @@
|
|
1
|
+
class TranslateController < ActionController::Base
|
2
|
+
# It seems users with active_record_store may get a "no :secret given" error if we don't disable csrf protection,
|
3
|
+
skip_before_filter :verify_authenticity_token
|
4
|
+
|
5
|
+
prepend_view_path(File.join(File.dirname(__FILE__), "..", "views"))
|
6
|
+
layout 'translate'
|
7
|
+
|
8
|
+
before_filter :init_translations
|
9
|
+
before_filter :set_locale
|
10
|
+
|
11
|
+
def index
|
12
|
+
initialize_keys
|
13
|
+
filter_by_key_pattern
|
14
|
+
filter_by_text_pattern
|
15
|
+
filter_by_translated_or_changed
|
16
|
+
sort_keys
|
17
|
+
paginate_keys
|
18
|
+
@total_entries = @keys.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def translate
|
22
|
+
I18n.backend.store_translations(@to_locale, Translate::Keys.to_deep_hash(params[:key]))
|
23
|
+
Translate::Storage.new(@to_locale).write_to_file
|
24
|
+
Translate::Log.new(@from_locale, @to_locale, params[:key].keys).write_to_file
|
25
|
+
force_init_translations # Force reload from YAML file
|
26
|
+
flash[:notice] = "Translations stored"
|
27
|
+
redirect_to params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern).merge({:action => :index})
|
28
|
+
end
|
29
|
+
|
30
|
+
def reload
|
31
|
+
Translate::Keys.files = nil
|
32
|
+
redirect_to :action => 'index'
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def initialize_keys
|
37
|
+
@files = Translate::Keys.files
|
38
|
+
@keys = (@files.keys.map(&:to_s) + Translate::Keys.new.i18n_keys(@from_locale)).uniq
|
39
|
+
@keys.reject! do |key|
|
40
|
+
from_text = lookup(@from_locale, key)
|
41
|
+
# When translating from one language to another, make sure there is a text to translate from.
|
42
|
+
# Always exclude non string translation objects as we don't support editing them in the UI.
|
43
|
+
(@from_locale != @to_locale && !from_text.present?) || (from_text.present? && !from_text.is_a?(String))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def lookup(locale, key)
|
48
|
+
I18n.backend.send(:lookup, locale, key)
|
49
|
+
end
|
50
|
+
helper_method :lookup
|
51
|
+
|
52
|
+
def filter_by_translated_or_changed
|
53
|
+
params[:filter] ||= 'all'
|
54
|
+
return if params[:filter] == 'all'
|
55
|
+
@keys.reject! do |key|
|
56
|
+
case params[:filter]
|
57
|
+
when 'untranslated'
|
58
|
+
lookup(@to_locale, key).present?
|
59
|
+
when 'translated'
|
60
|
+
lookup(@to_locale, key).blank?
|
61
|
+
when 'changed'
|
62
|
+
old_from_text(key).blank? || lookup(@from_locale, key) == old_from_text(key)
|
63
|
+
else
|
64
|
+
raise "Unknown filter '#{params[:filter]}'"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def filter_by_key_pattern
|
70
|
+
return if params[:key_pattern].blank?
|
71
|
+
@keys.reject! do |key|
|
72
|
+
case params[:key_type]
|
73
|
+
when "starts_with"
|
74
|
+
!key.starts_with?(params[:key_pattern])
|
75
|
+
when "contains"
|
76
|
+
key.index(params[:key_pattern]).nil?
|
77
|
+
else
|
78
|
+
raise "Unknown key_type '#{params[:key_type]}'"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def filter_by_text_pattern
|
84
|
+
return if params[:text_pattern].blank?
|
85
|
+
@keys.reject! do |key|
|
86
|
+
case params[:text_type]
|
87
|
+
when 'contains'
|
88
|
+
!lookup(@from_locale, key).present? || !lookup(@from_locale, key).to_s.downcase.index(params[:text_pattern].downcase)
|
89
|
+
when 'equals'
|
90
|
+
!lookup(@from_locale, key).present? || lookup(@from_locale, key).to_s.downcase != params[:text_pattern].downcase
|
91
|
+
else
|
92
|
+
raise "Unknown text_type '#{params[:text_type]}'"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def sort_keys
|
98
|
+
params[:sort_by] ||= "key"
|
99
|
+
case params[:sort_by]
|
100
|
+
when "key"
|
101
|
+
@keys.sort!
|
102
|
+
when "text"
|
103
|
+
@keys.sort! do |key1, key2|
|
104
|
+
if lookup(@from_locale, key1).present? && lookup(@from_locale, key2).present?
|
105
|
+
lookup(@from_locale, key1).to_s.downcase <=> lookup(@from_locale, key2).to_s.downcase
|
106
|
+
elsif lookup(@from_locale, key1).present?
|
107
|
+
-1
|
108
|
+
else
|
109
|
+
1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
raise "Unknown sort_by '#{params[:sort_by]}'"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def paginate_keys
|
118
|
+
params[:page] ||= 1
|
119
|
+
@paginated_keys = @keys[offset, per_page]
|
120
|
+
end
|
121
|
+
|
122
|
+
def offset
|
123
|
+
(params[:page].to_i - 1) * per_page
|
124
|
+
end
|
125
|
+
|
126
|
+
def per_page
|
127
|
+
50
|
128
|
+
end
|
129
|
+
helper_method :per_page
|
130
|
+
|
131
|
+
def init_translations
|
132
|
+
I18n.backend.send(:init_translations) unless I18n.backend.initialized?
|
133
|
+
end
|
134
|
+
|
135
|
+
def force_init_translations
|
136
|
+
I18n.backend.send(:init_translations)
|
137
|
+
end
|
138
|
+
|
139
|
+
def default_locale
|
140
|
+
I18n.default_locale
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_locale
|
144
|
+
session[:from_locale] ||= default_locale
|
145
|
+
session[:to_locale] ||= :en
|
146
|
+
session[:from_locale] = params[:from_locale] if params[:from_locale].present?
|
147
|
+
session[:to_locale] = params[:to_locale] if params[:to_locale].present?
|
148
|
+
@from_locale = session[:from_locale].to_sym
|
149
|
+
@to_locale = session[:to_locale].to_sym
|
150
|
+
end
|
151
|
+
|
152
|
+
def old_from_text(key)
|
153
|
+
return @old_from_text[key] if @old_from_text && @old_from_text[key]
|
154
|
+
@old_from_text = {}
|
155
|
+
text = key.split(".").inject(log_hash) do |hash, k|
|
156
|
+
hash ? hash[k] : nil
|
157
|
+
end
|
158
|
+
@old_from_text[key] = text
|
159
|
+
end
|
160
|
+
helper_method :old_from_text
|
161
|
+
|
162
|
+
def log_hash
|
163
|
+
@log_hash ||= Translate::Log.new(@from_locale, @to_locale, {}).read
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module TranslateHelper
|
2
|
+
def simple_filter(labels, param_name = 'filter', selected_value = nil)
|
3
|
+
selected_value ||= params[param_name]
|
4
|
+
filter = []
|
5
|
+
labels.each do |item|
|
6
|
+
if item.is_a?(Array)
|
7
|
+
type, label = item
|
8
|
+
else
|
9
|
+
type = label = item
|
10
|
+
end
|
11
|
+
if type.to_s == selected_value.to_s
|
12
|
+
filter << "<i>#{label}</i>"
|
13
|
+
else
|
14
|
+
link_params = params.merge({param_name.to_s => type})
|
15
|
+
link_params.merge!({"page" => nil}) if param_name.to_s != "page"
|
16
|
+
filter << link_to(label, link_params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
filter.join(" | ")
|
20
|
+
end
|
21
|
+
|
22
|
+
def n_lines(text, line_size)
|
23
|
+
n_lines = 1
|
24
|
+
if text.present?
|
25
|
+
n_lines = text.split("\n").size
|
26
|
+
if n_lines == 1 && text.length > line_size
|
27
|
+
n_lines = text.length / line_size + 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
n_lines
|
31
|
+
end
|
32
|
+
|
33
|
+
def translate_javascript_includes
|
34
|
+
sources = []
|
35
|
+
if File.exists?(File.join(Rails.root, "public", "javascripts", "prototype.js"))
|
36
|
+
sources << "/javascripts/prototype.js"
|
37
|
+
else
|
38
|
+
sources << "http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js"
|
39
|
+
end
|
40
|
+
sources << "http://www.google.com/jsapi"
|
41
|
+
sources.map do |src|
|
42
|
+
%Q{<script src="#{src}" type="text/javascript"></script>}
|
43
|
+
end.join("\n")
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,359 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
6
|
+
<title><%= h(@page_title) %></title>
|
7
|
+
|
8
|
+
<%= raw translate_javascript_includes %>
|
9
|
+
<script type="text/javascript">
|
10
|
+
google.load("language", "1");
|
11
|
+
|
12
|
+
function getGoogleTranslation(id, text, from_language, to_language) {
|
13
|
+
text = text.replace(/\{\{/, '__').replace(/\}\}/, '__')
|
14
|
+
google.language.translate(text, from_language, to_language, function(result) {
|
15
|
+
if (!result.error) {
|
16
|
+
result_text = result.translation.unescapeHTML().gsub(/__(.+)__/, function(match){
|
17
|
+
return '{{' + match[1] + '}}';
|
18
|
+
});
|
19
|
+
Form.Element.setValue(id, result_text);
|
20
|
+
}
|
21
|
+
});
|
22
|
+
|
23
|
+
}
|
24
|
+
|
25
|
+
/*
|
26
|
+
prototypeUtils.js from http://jehiah.com/
|
27
|
+
Licensed under Creative Commons.
|
28
|
+
version 1.0 December 20 2005
|
29
|
+
|
30
|
+
Contains:
|
31
|
+
+ Form.Element.setValue()
|
32
|
+
+ unpackToForm()
|
33
|
+
|
34
|
+
*/
|
35
|
+
|
36
|
+
/* Form.Element.setValue("fieldname/id","valueToSet") */
|
37
|
+
Form.Element.setValue = function(element,newValue) {
|
38
|
+
element_id = element;
|
39
|
+
element = $(element);
|
40
|
+
if (!element){element = document.getElementsByName(element_id)[0];}
|
41
|
+
if (!element){return false;}
|
42
|
+
var method = element.tagName.toLowerCase();
|
43
|
+
var parameter = Form.Element.SetSerializers[method](element,newValue);
|
44
|
+
}
|
45
|
+
|
46
|
+
Form.Element.SetSerializers = {
|
47
|
+
input: function(element,newValue) {
|
48
|
+
switch (element.type.toLowerCase()) {
|
49
|
+
case 'submit':
|
50
|
+
case 'hidden':
|
51
|
+
case 'password':
|
52
|
+
case 'text':
|
53
|
+
return Form.Element.SetSerializers.textarea(element,newValue);
|
54
|
+
case 'checkbox':
|
55
|
+
case 'radio':
|
56
|
+
return Form.Element.SetSerializers.inputSelector(element,newValue);
|
57
|
+
}
|
58
|
+
return false;
|
59
|
+
},
|
60
|
+
|
61
|
+
inputSelector: function(element,newValue) {
|
62
|
+
fields = document.getElementsByName(element.name);
|
63
|
+
for (var i=0;i<fields.length;i++){
|
64
|
+
if (fields[i].value == newValue){
|
65
|
+
fields[i].checked = true;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
},
|
69
|
+
|
70
|
+
textarea: function(element,newValue) {
|
71
|
+
element.value = newValue;
|
72
|
+
},
|
73
|
+
|
74
|
+
select: function(element,newValue) {
|
75
|
+
var value = '', opt, index = element.selectedIndex;
|
76
|
+
for (var i=0;i< element.options.length;i++){
|
77
|
+
if (element.options[i].value == newValue){
|
78
|
+
element.selectedIndex = i;
|
79
|
+
return true;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
function unpackToForm(data){
|
86
|
+
for (i in data){
|
87
|
+
Form.Element.setValue(i,data[i].toString());
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
</script>
|
92
|
+
|
93
|
+
|
94
|
+
<style type="text/css">
|
95
|
+
/*reset.css*/
|
96
|
+
/* v1.0 | 20080212 */
|
97
|
+
html, body, div, span, applet, object, iframe,
|
98
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
99
|
+
a, abbr, acronym, address, big, cite, code,
|
100
|
+
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
101
|
+
small, strike, strong, sub, sup, tt, var,
|
102
|
+
b, u, i, center,
|
103
|
+
dl, dt, dd, ol, ul, li,
|
104
|
+
fieldset, form, label, legend,
|
105
|
+
table, caption, tbody, tfoot, thead, tr, th, td {
|
106
|
+
margin: 0;
|
107
|
+
padding: 0;
|
108
|
+
border: 0;
|
109
|
+
outline: 0;
|
110
|
+
font-size: 100%;
|
111
|
+
vertical-align: baseline;
|
112
|
+
background: transparent;
|
113
|
+
}
|
114
|
+
body {
|
115
|
+
line-height: 1;
|
116
|
+
}
|
117
|
+
ol, ul {
|
118
|
+
list-style: none;
|
119
|
+
}
|
120
|
+
blockquote, q {
|
121
|
+
quotes: none;
|
122
|
+
}
|
123
|
+
blockquote:before, blockquote:after,
|
124
|
+
q:before, q:after {
|
125
|
+
content: '';
|
126
|
+
content: none;
|
127
|
+
}
|
128
|
+
|
129
|
+
/* remember to define focus styles! */
|
130
|
+
:focus {
|
131
|
+
outline: 0;
|
132
|
+
}
|
133
|
+
|
134
|
+
/* remember to highlight inserts somehow! */
|
135
|
+
ins {
|
136
|
+
text-decoration: none;
|
137
|
+
}
|
138
|
+
del {
|
139
|
+
text-decoration: line-through;
|
140
|
+
}
|
141
|
+
|
142
|
+
/* tables still need 'cellspacing="0"' in the markup */
|
143
|
+
table {
|
144
|
+
border-collapse: collapse;
|
145
|
+
border-spacing: 0;
|
146
|
+
}
|
147
|
+
/*clear fix*/
|
148
|
+
.clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden;}
|
149
|
+
.clearfix{display:inline-block;}
|
150
|
+
html[xmlns] .clearfix {
|
151
|
+
display: block;
|
152
|
+
}
|
153
|
+
* html .clearfix{height:1%;}
|
154
|
+
/*start layout*/
|
155
|
+
body{
|
156
|
+
background:#fff;
|
157
|
+
color:#333;
|
158
|
+
font-size:75%;
|
159
|
+
font-family:Arial;
|
160
|
+
margin:2em auto;
|
161
|
+
line-height:1.5em;
|
162
|
+
}
|
163
|
+
textarea,input,select{
|
164
|
+
font-family:Arial;
|
165
|
+
font-size:1em;
|
166
|
+
}
|
167
|
+
h1{
|
168
|
+
color:#d46021;
|
169
|
+
font-size:2em;
|
170
|
+
margin-bottom:0.5em;
|
171
|
+
}
|
172
|
+
h2{
|
173
|
+
text-align:left;
|
174
|
+
color:#d46021;
|
175
|
+
font-size:1.3em;
|
176
|
+
padding-left:0;
|
177
|
+
}
|
178
|
+
a{
|
179
|
+
color:#2158C7;
|
180
|
+
}
|
181
|
+
div#container{
|
182
|
+
width:960px;
|
183
|
+
margin:0 auto;
|
184
|
+
font-size:1em;
|
185
|
+
}
|
186
|
+
/*paging*/
|
187
|
+
div.paging{
|
188
|
+
margin-bottom:1em;
|
189
|
+
text-align:left;
|
190
|
+
}
|
191
|
+
div.paging div{
|
192
|
+
border:solid 1px red;
|
193
|
+
margin:1em 1em 0;
|
194
|
+
padding:0.5em;
|
195
|
+
border:solid 1px #d5d6d5;
|
196
|
+
background:#f1f1f1;
|
197
|
+
}
|
198
|
+
ul.paging{
|
199
|
+
display:inline-block;
|
200
|
+
}
|
201
|
+
ul.paging li{
|
202
|
+
display:block;
|
203
|
+
margin:0.2em 0;
|
204
|
+
float:left;
|
205
|
+
}
|
206
|
+
ul.paging li.selected a{
|
207
|
+
color:#fff;
|
208
|
+
background:#2158C7;
|
209
|
+
font-weight:bold;
|
210
|
+
padding:0.5em 0.7em;
|
211
|
+
}
|
212
|
+
ul.paging li a{
|
213
|
+
display:inline-block;
|
214
|
+
line-height:1em;
|
215
|
+
padding:0.5em 0.5em;
|
216
|
+
}
|
217
|
+
/*forms filter*/
|
218
|
+
fieldset{
|
219
|
+
padding:1em;
|
220
|
+
margin:1em;
|
221
|
+
border:solid 2px #d46021;
|
222
|
+
}
|
223
|
+
legend{
|
224
|
+
font-size:1.2em;
|
225
|
+
font-weight:bold;
|
226
|
+
padding:0 1em;
|
227
|
+
padding-bottom:0.5em;
|
228
|
+
}
|
229
|
+
label{
|
230
|
+
font-weight:bold;
|
231
|
+
}
|
232
|
+
fieldset span{padding-right:0.5em;}
|
233
|
+
div#show-sort label,
|
234
|
+
div#languages label,
|
235
|
+
div#filter-pattern label{
|
236
|
+
display:inline-block;
|
237
|
+
width:100px;
|
238
|
+
line-height:2em;
|
239
|
+
}
|
240
|
+
div#show-sort select,
|
241
|
+
div#languages select,
|
242
|
+
div#filter-pattern select{
|
243
|
+
width:120px;
|
244
|
+
margin-right:0.5em;
|
245
|
+
}
|
246
|
+
div#show-sort input.text-default,
|
247
|
+
div#languages input.text-default,
|
248
|
+
div#filter-pattern input.text-default{
|
249
|
+
width:200px;
|
250
|
+
}
|
251
|
+
p.hits{
|
252
|
+
margin-top:1em;
|
253
|
+
}
|
254
|
+
/*translation edit*/
|
255
|
+
div.translations{
|
256
|
+
margin:1em;
|
257
|
+
padding:1em;
|
258
|
+
border:solid 2px #d46021;
|
259
|
+
}
|
260
|
+
div.translations h2{
|
261
|
+
margin-bottom:1em;
|
262
|
+
}
|
263
|
+
p.translate{
|
264
|
+
background:red;
|
265
|
+
border:solid 1px #d5d6d5;
|
266
|
+
background:#f1f1f1;
|
267
|
+
margin:0.5em;
|
268
|
+
padding:0.7em 0.5em 0.5em 1.5em;
|
269
|
+
}
|
270
|
+
div.translation{
|
271
|
+
padding:1em;
|
272
|
+
border-bottom:solid 0.2em #d46021;
|
273
|
+
margin:0 1em 1em 1.6em;
|
274
|
+
}
|
275
|
+
div.translation input, div.translation textarea{
|
276
|
+
width:98%;
|
277
|
+
margin:1em 0;
|
278
|
+
display:inline-block;
|
279
|
+
padding:0.3em;
|
280
|
+
}
|
281
|
+
div.translation textarea{
|
282
|
+
height:50px;
|
283
|
+
}
|
284
|
+
div.translation em strong{
|
285
|
+
color:#333;
|
286
|
+
padding-right:0.5em;
|
287
|
+
}
|
288
|
+
p.translation em{
|
289
|
+
display:block;
|
290
|
+
font-size:0.8333em;
|
291
|
+
}
|
292
|
+
div.translation a{
|
293
|
+
padding:1em;
|
294
|
+
}
|
295
|
+
div.translation input.btnDefault{
|
296
|
+
margin:0 0 1em;
|
297
|
+
width:auto;
|
298
|
+
}
|
299
|
+
.focus-text{
|
300
|
+
font-weight:bold;
|
301
|
+
}
|
302
|
+
div.selected{
|
303
|
+
margin:0 1em 1em 1em;
|
304
|
+
border-left:solid 0.6em #d46021;
|
305
|
+
border-right:solid 0.2em #d46021;
|
306
|
+
border-top:solid 0.2em #d46021;
|
307
|
+
background:#f1f1f1;
|
308
|
+
}
|
309
|
+
.display{display:block !important;}
|
310
|
+
/*feedback*/
|
311
|
+
div#notice, div#error {
|
312
|
+
font-size:1em;
|
313
|
+
margin:1em;
|
314
|
+
padding: 1em;
|
315
|
+
border: 1px solid red;
|
316
|
+
}
|
317
|
+
div#notice span, div#error span{
|
318
|
+
font-size:1.5em;
|
319
|
+
}
|
320
|
+
|
321
|
+
div#error {
|
322
|
+
background-color: #F3C6CC;
|
323
|
+
color: red;
|
324
|
+
}
|
325
|
+
div#notice {
|
326
|
+
border-color: #72A974;
|
327
|
+
color: #597B5C;
|
328
|
+
background-color: #BCFFBD;
|
329
|
+
}
|
330
|
+
</style>
|
331
|
+
<script type="text/javascript">
|
332
|
+
onload = function (){
|
333
|
+
$$("div.translation input, div.translation textarea").each(function (e){
|
334
|
+
Event.observe(e,'focus', function (elm){
|
335
|
+
this.up(1).down(".translation-text").addClassName("focus-text");
|
336
|
+
this.up(1).addClassName("selected");
|
337
|
+
});
|
338
|
+
Event.observe(e,'blur', function (elm,e){
|
339
|
+
this.up(1).down(".translation-text").removeClassName("focus-text");
|
340
|
+
this.up(1).removeClassName("selected");
|
341
|
+
});
|
342
|
+
});
|
343
|
+
}
|
344
|
+
</script>
|
345
|
+
</head>
|
346
|
+
<body>
|
347
|
+
<div id="container">
|
348
|
+
<% if @page_title -%><h1><%=h @page_title %></h1><% end -%>
|
349
|
+
<% [:notice, :error].each do |message| %>
|
350
|
+
<%if flash[message] %>
|
351
|
+
<div id="<%= message %>">
|
352
|
+
<span><%= h(flash[message]) if flash[message] %></span>
|
353
|
+
</div>
|
354
|
+
<% end %>
|
355
|
+
<% end %>
|
356
|
+
<%= yield %>
|
357
|
+
</div>
|
358
|
+
</body>
|
359
|
+
</html>
|