iye 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ # IYE
2
+
3
+ IYE - short for I18N YAML Editor - makes it easy to translate your Rails I18N files and
4
+ keep them up to date. Unlike a lot of other tools in this space, IYE works directly on the
5
+ YAML files instead of keeping a separate database in sync. This has several benefits:
6
+
7
+ * Branching and diffing is trivial
8
+ * It does not alter the workflow for developers etc., whom can continue editing the
9
+ YAML files directly
10
+ * If your YAML files are organized in subfolders, this structure is kept intact
11
+
12
+ ![IYE yaml editor](http://f.cl.ly/items/2K2V2i3N2R2X1L2F051F/Sk%C3%A6rmbillede%202012-09-18%20kl.%2013.36.07.png)
13
+
14
+ ## Prerequisites
15
+
16
+ You need to understand a few things about IYE for it to make sense, mainly:
17
+
18
+ * IYE does not create new keys - keys must exist for at least one locale in the YAML files
19
+ * IYE does not create new locales - at least one key must exist for each locale in the YAML files
20
+
21
+ ## Workflow
22
+
23
+ 1. Install IYE:
24
+
25
+ $ gem install iye
26
+
27
+ 2. Navigate to the folder containing your YAML files and start IYE:
28
+
29
+ $ iye .
30
+
31
+ At this point IYE loads all translation keys for all locales, and creates any
32
+ keys that might be missing for existing locales.
33
+
34
+ 3. Point browser at [http://localhost:5050](http://localhost:5050)
35
+ 4. Make changes and press 'Save' - each time you do this, all the keys will be
36
+ written to their original YAML files, which you can confirm e.g. by using
37
+ `git diff`.
38
+
39
+ ## Development
40
+
41
+ The source ships with a `config.ru` suitable for development use with `shotgun(1)`:
42
+
43
+ shotgun -p 5050
44
+
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.libs << "lib"
6
+ t.pattern = "test/**/test_*.rb"
7
+ end
8
+
9
+ task :default => :test
data/bin/iye ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "i18n_yaml_editor"
4
+
5
+ iye = I18nYamlEditor::App.new(ARGV[0])
6
+ iye.start
@@ -0,0 +1,40 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "iye"
3
+ s.version = "1.0.0"
4
+ s.date = "2012-09-18"
5
+ s.summary = "I18n Yaml Editor"
6
+ s.email = "hv@firmafon.dk"
7
+ s.homepage = "http://github.com/firmafon/iye"
8
+ s.description = "I18n YAML Editor"
9
+ s.authors = ["Harry Vangberg"]
10
+ s.executables << "iye"
11
+ s.files = [
12
+ "README.md",
13
+ "Rakefile",
14
+ "iye.gemspec",
15
+ "bin/iye",
16
+ "lib/i18n_yaml_editor.rb",
17
+ "lib/i18n_yaml_editor/app.rb",
18
+ "lib/i18n_yaml_editor/category.rb",
19
+ "lib/i18n_yaml_editor/key.rb",
20
+ "lib/i18n_yaml_editor/store.rb",
21
+ "lib/i18n_yaml_editor/transformation.rb",
22
+ "lib/i18n_yaml_editor/translation.rb",
23
+ "lib/i18n_yaml_editor/web.rb",
24
+ "views/layout.erb",
25
+ "views/categories.html.erb",
26
+ "views/translations.html.erb"
27
+ ]
28
+ s.test_files = [
29
+ "test/test_helper.rb",
30
+ "test/unit/test_app.rb",
31
+ "test/unit/test_category.rb",
32
+ "test/unit/test_key.rb",
33
+ "test/unit/test_store.rb",
34
+ "test/unit/test_transformation.rb",
35
+ "test/unit/test_translation.rb"
36
+ ]
37
+ s.add_dependency "cuba", ">= 3"
38
+ s.add_dependency "tilt", ">= 1.3"
39
+ s.add_dependency "psych", ">= 1.3.4"
40
+ end
@@ -0,0 +1,13 @@
1
+ module I18nYamlEditor
2
+ class << self
3
+ attr_accessor :app
4
+ end
5
+ end
6
+
7
+ require "i18n_yaml_editor/app"
8
+ require "i18n_yaml_editor/category"
9
+ require "i18n_yaml_editor/key"
10
+ require "i18n_yaml_editor/store"
11
+ require "i18n_yaml_editor/transformation"
12
+ require "i18n_yaml_editor/translation"
13
+ require "i18n_yaml_editor/web"
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require "psych"
4
+ require "yaml"
5
+
6
+ require "i18n_yaml_editor"
7
+ require "i18n_yaml_editor/web"
8
+ require "i18n_yaml_editor/store"
9
+
10
+ module I18nYamlEditor
11
+ class App
12
+ def initialize path
13
+ @path = File.expand_path(path)
14
+ @store = Store.new
15
+ I18nYamlEditor.app = self
16
+ end
17
+
18
+ attr_accessor :store
19
+
20
+ def start
21
+ $stdout.puts " * Loading translations from #{@path}"
22
+ load_translations
23
+
24
+ $stdout.puts " * Creating missing translations"
25
+ store.create_missing_keys
26
+
27
+ $stdout.puts " * Starting web editor at port 5050"
28
+ Rack::Server.start :app => Web, :Port => 5050
29
+ end
30
+
31
+ def load_translations
32
+ files = Dir[@path + "/**/*.yml"]
33
+ files.each {|file|
34
+ yaml = YAML.load_file(file)
35
+ store.from_yaml(yaml, file)
36
+ }
37
+ end
38
+
39
+ def save_translations
40
+ files = store.to_yaml
41
+ files.each {|file, yaml|
42
+ File.open(file, "w", encoding: "utf-8") {|f| f << yaml.to_yaml}
43
+ }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require "set"
4
+
5
+ module I18nYamlEditor
6
+ class Category
7
+ attr_accessor :name, :keys
8
+
9
+ def initialize attributes={}
10
+ @name = attributes[:name]
11
+ @keys = Set.new
12
+ end
13
+
14
+ def add_key key
15
+ self.keys.add(key)
16
+ end
17
+
18
+ def complete?
19
+ self.keys.all? {|key| key.complete?}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require "set"
4
+
5
+ module I18nYamlEditor
6
+ class Key
7
+ attr_accessor :name, :translations
8
+
9
+ def initialize attributes={}
10
+ @name = attributes[:name]
11
+ @translations = Set.new
12
+ end
13
+
14
+ def add_translation translation
15
+ self.translations.add(translation)
16
+ end
17
+
18
+ def category
19
+ @category ||= self.name.split(".").first
20
+ end
21
+
22
+ def complete?
23
+ self.translations.all? {|t| t.text.to_s !~ /\A\s*\z/} || empty?
24
+ end
25
+
26
+ def empty?
27
+ self.translations.all? {|t| t.text.to_s =~ /\A\s*\z/}
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,115 @@
1
+ # encoding: utf-8
2
+
3
+ require "set"
4
+ require "pathname"
5
+
6
+ require "i18n_yaml_editor/transformation"
7
+ require "i18n_yaml_editor/category"
8
+ require "i18n_yaml_editor/key"
9
+ require "i18n_yaml_editor/translation"
10
+
11
+ module I18nYamlEditor
12
+ class DuplicateTranslationError < StandardError; end
13
+
14
+ class Store
15
+ include Transformation
16
+
17
+ attr_accessor :categories, :keys, :translations, :locales
18
+
19
+ def initialize
20
+ @categories = {}
21
+ @keys = {}
22
+ @translations = {}
23
+ @locales = Set.new
24
+ end
25
+
26
+ def add_translation translation
27
+ if existing = self.translations[translation.name]
28
+ message = "#{translation.name} detected in #{translation.file} and #{existing.file}"
29
+ raise DuplicateTranslationError.new(message)
30
+ end
31
+
32
+ self.translations[translation.name] = translation
33
+
34
+ add_locale(translation.locale)
35
+
36
+ key = (self.keys[translation.key] ||= Key.new(name: translation.key))
37
+ key.add_translation(translation)
38
+
39
+ category = (self.categories[key.category] ||= Category.new(name: key.category))
40
+ category.add_key(key)
41
+ end
42
+
43
+ def add_key key
44
+ self.keys[key.name] = key
45
+ end
46
+
47
+ def add_locale locale
48
+ self.locales.add(locale)
49
+ end
50
+
51
+ def filter_keys options={}
52
+ filters = []
53
+ if options.has_key?(:key)
54
+ filters << lambda {|k| k.name =~ options[:key]}
55
+ end
56
+ if options.has_key?(:complete)
57
+ filters << lambda {|k| k.complete? == options[:complete]}
58
+ end
59
+ if options.has_key?(:empty)
60
+ filters << lambda {|k| k.empty? == options[:empty]}
61
+ end
62
+ if options.has_key?(:text)
63
+ filters << lambda {|k|
64
+ k.translations.any? {|t| t.text =~ options[:text]}
65
+ }
66
+ end
67
+
68
+ self.keys.select {|name, key|
69
+ filters.all? {|filter| filter.call(key)}
70
+ }
71
+ end
72
+
73
+ def create_missing_keys
74
+ self.keys.each {|name, key|
75
+ missing_locales = self.locales - key.translations.map(&:locale)
76
+ missing_locales.each {|locale|
77
+ translation = key.translations.first
78
+
79
+ # this just replaces the locale part of the file name. should
80
+ # be possible to do in a simpler way. gsub, baby.
81
+ path = Pathname.new(translation.file)
82
+ dirs, file = path.split
83
+ file = file.to_s.split(".")
84
+ file[-2] = locale
85
+ file = file.join(".")
86
+ path = dirs.join(file).to_s
87
+
88
+ new_translation = Translation.new(name: "#{locale}.#{key.name}", file: path)
89
+ add_translation(new_translation)
90
+ }
91
+ }
92
+ end
93
+
94
+ def from_yaml yaml, file=nil
95
+ translations = flatten_hash(yaml)
96
+ translations.each {|name, text|
97
+ translation = Translation.new(name: name, text: text, file: file)
98
+ add_translation(translation)
99
+ }
100
+ end
101
+
102
+ def to_yaml
103
+ result = {}
104
+ files = self.translations.values.group_by(&:file)
105
+ files.each {|file, translations|
106
+ file_result = {}
107
+ translations.each {|translation|
108
+ file_result[translation.name] = translation.text
109
+ }
110
+ result[file] = nest_hash(file_result)
111
+ }
112
+ result
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module I18nYamlEditor
4
+ module Transformation
5
+ def flatten_hash hash, namespace=[], tree={}
6
+ hash.each {|key, value|
7
+ child_ns = namespace.dup << key
8
+ if value.is_a?(Hash)
9
+ flatten_hash value, child_ns, tree
10
+ else
11
+ tree[child_ns.join(".")] = value
12
+ end
13
+ }
14
+ tree
15
+ end
16
+ module_function :flatten_hash
17
+
18
+ def nest_hash hash
19
+ result = {}
20
+ hash.each {|key, value|
21
+ sub_result = result
22
+ keys = key.split(".")
23
+ keys.each_with_index {|k, idx|
24
+ if keys.size - 1 == idx
25
+ sub_result[k.to_sym] = value
26
+ else
27
+ sub_result = (sub_result[k.to_sym] ||= {})
28
+ end
29
+ }
30
+ }
31
+ result
32
+ end
33
+ module_function :nest_hash
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module I18nYamlEditor
4
+ class Translation
5
+ attr_accessor :name, :file, :text
6
+
7
+ def initialize attributes={}
8
+ @name, @file, @text = attributes.values_at(:name, :file, :text)
9
+ end
10
+
11
+ def key
12
+ @key ||= self.name.split(".")[1..-1].join(".")
13
+ end
14
+
15
+ def locale
16
+ @locale ||= self.name.split(".").first
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require "cuba"
4
+ require "cuba/render"
5
+
6
+ require "i18n_yaml_editor/app"
7
+
8
+ module I18nYamlEditor
9
+ class Web < Cuba
10
+ plugin Cuba::Render
11
+
12
+ settings[:render][:template_engine] = "erb"
13
+ settings[:render][:views] = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "views"))
14
+
15
+ use Rack::ShowExceptions
16
+
17
+ def app
18
+ I18nYamlEditor.app
19
+ end
20
+
21
+ define do
22
+ on get, root do
23
+ on param("filters") do |filters|
24
+ options = {}
25
+ options[:key] = /#{filters["key"]}/ if filters["key"].to_s.size > 0
26
+ options[:text] = /#{filters["text"]}/i if filters["text"].to_s.size > 0
27
+ options[:complete] = false if filters["incomplete"] == "on"
28
+ options[:empty] = true if filters["empty"] == "on"
29
+
30
+ keys = app.store.filter_keys(options)
31
+
32
+ res.write view("translations.html", keys: keys, filters: filters)
33
+ end
34
+
35
+ on default do
36
+ categories = app.store.categories.sort
37
+ res.write view("categories.html", categories: categories, filters: {})
38
+ end
39
+ end
40
+
41
+ on post, "update" do
42
+ if translations = req["translations"]
43
+ translations.each {|name, text|
44
+ app.store.translations[name].text = text
45
+ }
46
+ app.save_translations
47
+ end
48
+
49
+ res.redirect "/?#{Rack::Utils.build_nested_query(filters: req["filters"])}"
50
+ end
51
+
52
+ on get, "debug" do
53
+ res.write partial("debug.html", translations: app.store.translations.values)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ require "minitest/autorun"
2
+ require "i18n_yaml_editor"
3
+
4
+ class MiniTest::Unit::TestCase
5
+ include I18nYamlEditor
6
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/app"
5
+
6
+ class TestApp < MiniTest::Unit::TestCase
7
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/category"
5
+
6
+ class TestCategory < MiniTest::Unit::TestCase
7
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/key"
5
+
6
+ class TestKey < MiniTest::Unit::TestCase
7
+ def test_category
8
+ key = Key.new(name: "session.login")
9
+ assert_equal "session", key.category
10
+ end
11
+
12
+ def test_complete_with_text_for_all_translations
13
+ key = Key.new(name: "session.login")
14
+ key.add_translation Translation.new(name: "da.session.login", text: "Log ind")
15
+ key.add_translation Translation.new(name: "en.session.login", text: "Sign in")
16
+
17
+ assert key.complete?
18
+ end
19
+
20
+ def test_complete_with_no_texts
21
+ key = Key.new(name: "session.login")
22
+ key.add_translation Translation.new(name: "da.session.login")
23
+ key.add_translation Translation.new(name: "en.session.login")
24
+
25
+ assert key.complete?
26
+ end
27
+
28
+ def test_not_complete_without_text_for_some_translations
29
+ key = Key.new(name: "session.login")
30
+ key.add_translation Translation.new(name: "da.session.login", text: "Log ind")
31
+ key.add_translation Translation.new(name: "en.session.login")
32
+
33
+ assert_equal false, key.complete?
34
+ end
35
+
36
+ def testemptycomplete_with_no_texts
37
+ key = Key.new(name: "session.login")
38
+ key.add_translation Translation.new(name: "da.session.login")
39
+ key.add_translation Translation.new(name: "en.session.login")
40
+
41
+ assert key.empty?
42
+ end
43
+ end
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/store"
5
+
6
+ class TestStore < MiniTest::Unit::TestCase
7
+ def test_add_translations
8
+ store = Store.new
9
+ translation = Translation.new(:name => "da.session.login")
10
+
11
+ store.add_translation(translation)
12
+
13
+ assert_equal 1, store.translations.size
14
+ assert_equal translation, store.translations[translation.name]
15
+
16
+ assert_equal 1, store.keys.size
17
+ assert_equal Set.new([translation]), store.keys["session.login"].translations
18
+
19
+ assert_equal 1, store.categories.size
20
+ assert_equal %w(session.login), store.categories["session"].keys.map(&:name)
21
+
22
+ assert_equal 1, store.locales.size
23
+ assert_equal %w(da), store.locales.to_a
24
+ end
25
+
26
+ def test_add_duplicate_translation
27
+ store = Store.new
28
+ t1 = Translation.new(:name => "da.session.login")
29
+ t2 = Translation.new(:name => "da.session.login")
30
+ store.add_translation(t1)
31
+
32
+ assert_raises(DuplicateTranslationError) {
33
+ store.add_translation(t2)
34
+ }
35
+ end
36
+
37
+ def test_filter_keys_on_key
38
+ store = Store.new
39
+ store.add_key(Key.new(name: "session.login"))
40
+ store.add_key(Key.new(name: "session.logout"))
41
+
42
+ result = store.filter_keys(key: /login/)
43
+
44
+ assert_equal 1, result.size
45
+ assert_equal %w(session.login), result.keys
46
+ end
47
+
48
+ def test_filter_keys_on_complete
49
+ store = Store.new
50
+ store.add_translation Translation.new(name: "da.session.login", text: "Log ind")
51
+ store.add_translation Translation.new(name: "en.session.login")
52
+ store.add_translation Translation.new(name: "da.session.logout", text: "Log ud")
53
+
54
+ result = store.filter_keys(complete: false)
55
+
56
+ assert_equal 1, result.size
57
+ assert_equal %w(session.login), result.keys
58
+ end
59
+
60
+ def test_filter_keys_on_empty
61
+ store = Store.new
62
+ store.add_translation Translation.new(name: "da.session.login", text: "Log ind")
63
+ store.add_translation Translation.new(name: "da.session.logout")
64
+
65
+ result = store.filter_keys(empty: true)
66
+
67
+ assert_equal 1, result.size
68
+ assert_equal %w(session.logout), result.keys
69
+ end
70
+
71
+ def test_filter_keys_on_text
72
+ store = Store.new
73
+ store.add_translation Translation.new(name: "da.session.login", text: "Log ind")
74
+ store.add_translation Translation.new(name: "da.session.logout", text: "Log ud")
75
+ store.add_translation Translation.new(name: "da.app.name", text: "Translator")
76
+
77
+ result = store.filter_keys(text: /Log/)
78
+
79
+ assert_equal 2, result.size
80
+ assert_equal %w(session.login session.logout).sort, result.keys.sort
81
+ end
82
+
83
+ def test_create_missing_translations
84
+ store = Store.new
85
+ store.add_translation Translation.new(name: "da.session.login", text: "Log ind", file: "/tmp/session.da.yml")
86
+ store.add_locale("en")
87
+
88
+ store.create_missing_keys
89
+
90
+ assert(translation = store.translations["en.session.login"])
91
+ assert_equal "en.session.login", translation.name
92
+ assert_equal "/tmp/session.en.yml", translation.file
93
+ assert_nil translation.text
94
+ end
95
+
96
+ def test_create_missing_translations_in_top_level_file
97
+ store = Store.new
98
+ store.add_translation Translation.new(name: "da.app_name", text: "Oversætter", file: "/tmp/da.yml")
99
+ store.add_locale("en")
100
+
101
+ store.create_missing_keys
102
+
103
+ assert(translation = store.translations["en.app_name"])
104
+ assert_equal "en.app_name", translation.name
105
+ assert_equal "/tmp/en.yml", translation.file
106
+ assert_nil translation.text
107
+ end
108
+
109
+ def test_from_yaml
110
+ input = {
111
+ da: {
112
+ session: {login: "Log ind"}
113
+ }
114
+ }
115
+ store = Store.new
116
+
117
+ store.from_yaml(input)
118
+
119
+ assert_equal 1, store.translations.size
120
+ translation = store.translations["da.session.login"]
121
+ assert_equal "da.session.login", translation.name
122
+ assert_equal "Log ind", translation.text
123
+ end
124
+
125
+ def test_to_yaml
126
+ expected = {
127
+ "/tmp/session.da.yml" => {
128
+ da: {
129
+ session: {
130
+ login: "Log ind",
131
+ logout: "Log ud"
132
+ }
133
+ }
134
+ },
135
+ "/tmp/session.en.yml" => {
136
+ en: {
137
+ session: {
138
+ login: "Sign in"
139
+ }
140
+ }
141
+ },
142
+ "/tmp/da.yml" => {
143
+ da: {
144
+ app_name: "Oversætter"
145
+ }
146
+ }
147
+ }
148
+
149
+ store = Store.new
150
+ store.add_translation Translation.new(name: "da.session.login", text: "Log ind", file: "/tmp/session.da.yml")
151
+ store.add_translation Translation.new(name: "en.session.login", text: "Sign in", file: "/tmp/session.en.yml")
152
+ store.add_translation Translation.new(name: "da.session.logout", text: "Log ud", file: "/tmp/session.da.yml")
153
+ store.add_translation Translation.new(name: "da.app_name", text: "Oversætter", file: "/tmp/da.yml")
154
+
155
+ assert_equal expected, store.to_yaml
156
+ end
157
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/transformation"
5
+
6
+ class TestTransformation < MiniTest::Unit::TestCase
7
+ def test_flatten_hash
8
+ input = {
9
+ da: {
10
+ session: {login: "Log ind", logout: "Log ud"}
11
+ },
12
+ en: {
13
+ session: {login: "Log in", logout: "Log out"}
14
+ }
15
+ }
16
+ expected = {
17
+ "da.session.login" => "Log ind",
18
+ "da.session.logout" => "Log ud",
19
+ "en.session.login" => "Log in",
20
+ "en.session.logout" => "Log out"
21
+ }
22
+
23
+ assert_equal expected, Transformation.flatten_hash(input)
24
+ end
25
+
26
+ def test_nest_hash
27
+ input = {
28
+ "da.session.login" => "Log ind",
29
+ "da.session.logout" => "Log ud",
30
+ "en.session.login" => "Log in",
31
+ "en.session.logout" => "Log out"
32
+ }
33
+ expected = {
34
+ da: {
35
+ session: {login: "Log ind", logout: "Log ud"}
36
+ },
37
+ en: {
38
+ session: {login: "Log in", logout: "Log out"}
39
+ }
40
+ }
41
+
42
+ assert_equal expected, Transformation.nest_hash(input)
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require "test_helper"
4
+ require "i18n_yaml_editor/translation"
5
+
6
+ class TestTranslation < MiniTest::Unit::TestCase
7
+ def test_key
8
+ translation = Translation.new(name: "da.session.login")
9
+ assert_equal "session.login", translation.key
10
+ end
11
+
12
+ def test_locale
13
+ translation = Translation.new(name: "da.session.login")
14
+ assert_equal "da", translation.locale
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ <table class="categories" cellspacing="0">
2
+ <% categories.each do |name, category| %>
3
+ <tr class="category <%= "incomplete" unless category.complete? %>">
4
+ <td class="key">
5
+ <a class="text" href="/?filters[key]=^<%= category.name %>"><%= category.name %></a>
6
+ </td>
7
+ </tr>
8
+ <% end %>
9
+ </table>
@@ -0,0 +1,139 @@
1
+ <html>
2
+ <head>
3
+ <title>IYE</title>
4
+ <style>
5
+ /* GENERAL */
6
+ body {
7
+ font-family: sans-serif;
8
+ font-size: 0.7em;
9
+ margin: 0;
10
+ margin-top: 60px;
11
+ }
12
+
13
+ /* HEADER */
14
+ .header {
15
+ margin: 0;
16
+ padding: 1em;
17
+ background-color: #fff;
18
+ z-index: 999 !important;
19
+ position: fixed;
20
+ top: 0;
21
+ width: 100%;
22
+ -webkit-box-shadow: 0 8px 6px -6px #ccc;
23
+ -moz-box-shadow: 0 8px 6px -6px #ccc;
24
+ box-shadow: 0 8px 6px -6px #ccc;
25
+ }
26
+
27
+ .header h1, .header form {
28
+ display: inline;
29
+ vertical-align: middle;
30
+ }
31
+
32
+ .header h1 {
33
+ margin-right: 2em;
34
+ }
35
+
36
+ .pull-right {
37
+ float:right;
38
+ padding-right:20px;
39
+ }
40
+
41
+ /* TRANSLATIONS */
42
+ table.keys {
43
+ width: 100%;
44
+ }
45
+
46
+ table.keys .key {
47
+ padding-left: 0.5em;
48
+ }
49
+
50
+ table.keys tr.translation > td {
51
+ border-bottom: 1px solid #bbb;
52
+ }
53
+
54
+ table.keys tr.translation.incomplete .key .text {
55
+ border-bottom: 3px solid #f33;
56
+ }
57
+
58
+ table.keys tr.translation.empty .key .text {
59
+ border-bottom: 3px solid #33f;
60
+ }
61
+
62
+ table.keys td.key {
63
+ width: 300px;
64
+ }
65
+
66
+ table.locales td.value input {
67
+ width: 600px;
68
+ }
69
+
70
+ /* CATEGORIES */
71
+
72
+ table.categories {
73
+ width: 100%;
74
+ }
75
+
76
+ table.categories .key {
77
+ padding-left: 0.5em;
78
+ }
79
+
80
+ table.categories tr.category a {
81
+ text-decoration: none;
82
+ }
83
+
84
+ table.categories tr.category > td {
85
+ border-bottom: 1px solid #bbb;
86
+ }
87
+
88
+ table.categories tr.category > td {
89
+ padding-top: 1em;
90
+ padding-bottom: 1em;
91
+ }
92
+
93
+ table.categories tr.category.incomplete .key .text {
94
+ border-bottom: 3px solid #f33;
95
+ }
96
+ </style>
97
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
98
+ </head>
99
+
100
+ <body>
101
+ <div class="header">
102
+ <h1>IYE</h1>
103
+
104
+ <form action="/" method="get">
105
+ <label>
106
+ Key:
107
+ <input name="filters[key]" type="text" value="<%= filters["key"] %>" />
108
+ </label>
109
+
110
+ <label>
111
+ Text:
112
+ <input name="filters[text]" type="text" value="<%= filters["text"] %>" />
113
+ </label>
114
+
115
+ <label>
116
+ <input name="filters[incomplete]" type="checkbox" <%= "checked=checked" if filters["incomplete"] == "on" %> />
117
+ Incomplete
118
+ </label>
119
+ <label>
120
+ <input name="filters[empty]" type="checkbox" <%= "checked=checked" if filters["empty"] == "on" %> />
121
+ Empty
122
+ </label>
123
+
124
+ <input type="submit" value="Filter" />
125
+ </form>
126
+
127
+ <form action="/" method="get">
128
+ <input type="submit" value="Clear" />
129
+ </form>
130
+
131
+ <div class="pull-right">
132
+ </div>
133
+ </div>
134
+
135
+ <div class="content">
136
+ <%= content %>
137
+ </div>
138
+ </body>
139
+ </html>
@@ -0,0 +1,44 @@
1
+ <form id="updateform" action="/update" method="post">
2
+ <% filters.each do |filter, value| %>
3
+ <input name="filters[<%= filter %>" type="hidden" value="<%= value %>"/>
4
+ <% end %>
5
+
6
+ <table class="keys" cellspacing="0">
7
+ <% keys.each do |name, key| %>
8
+ <tr class="translation
9
+ <%= "incomplete" unless key.complete? %>
10
+ <%= "empty" if key.empty? %>
11
+ ">
12
+ <td class="key">
13
+ <span class="text"><%= key.name %></span>
14
+ </td>
15
+ <td class="translations">
16
+ <table class="locales">
17
+ <% key.translations.sort_by(&:locale).each do |translation| %>
18
+ <tr>
19
+ <td class="locale"><%= translation.locale %>:</td>
20
+ <td class="value">
21
+ <% if translation.text.nil? || translation.text.is_a?(String) %>
22
+ <input type="text" name="translations[<%= translation.name %>]" value="<%= translation.text %>" />
23
+ <% else %>
24
+ [read only]
25
+ <%= translation.text.class.name %>
26
+ <%= Rack::Utils.escape_html translation.text.inspect %>
27
+ <% end %>
28
+ </td>
29
+ </tr>
30
+ <% end %>
31
+ </table>
32
+ </td>
33
+ </tr>
34
+ <% end %>
35
+ </table>
36
+
37
+ <p class="pull-right"><input type="submit" id="save" value="Save" /></p>
38
+ </form>
39
+ <script type="text/javascript">
40
+ $('.header .pull-right').html("<button id='save-on-top'>Save</button>")
41
+ $('#save-on-top').click(function() {
42
+ $('#updateform').submit();
43
+ });
44
+ </script>
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iye
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Harry Vangberg
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cuba
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: tilt
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: psych
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.4
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.4
62
+ description: I18n YAML Editor
63
+ email: hv@firmafon.dk
64
+ executables:
65
+ - iye
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - README.md
70
+ - Rakefile
71
+ - iye.gemspec
72
+ - bin/iye
73
+ - lib/i18n_yaml_editor.rb
74
+ - lib/i18n_yaml_editor/app.rb
75
+ - lib/i18n_yaml_editor/category.rb
76
+ - lib/i18n_yaml_editor/key.rb
77
+ - lib/i18n_yaml_editor/store.rb
78
+ - lib/i18n_yaml_editor/transformation.rb
79
+ - lib/i18n_yaml_editor/translation.rb
80
+ - lib/i18n_yaml_editor/web.rb
81
+ - views/layout.erb
82
+ - views/categories.html.erb
83
+ - views/translations.html.erb
84
+ - test/test_helper.rb
85
+ - test/unit/test_app.rb
86
+ - test/unit/test_category.rb
87
+ - test/unit/test_key.rb
88
+ - test/unit/test_store.rb
89
+ - test/unit/test_transformation.rb
90
+ - test/unit/test_translation.rb
91
+ homepage: http://github.com/firmafon/iye
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.23
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: I18n Yaml Editor
115
+ test_files:
116
+ - test/test_helper.rb
117
+ - test/unit/test_app.rb
118
+ - test/unit/test_category.rb
119
+ - test/unit/test_key.rb
120
+ - test/unit/test_store.rb
121
+ - test/unit/test_transformation.rb
122
+ - test/unit/test_translation.rb