iye 1.0.0

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.
@@ -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