phraseapp-in-context-editor-ruby 1.0.0rc1

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,21 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module PhraseappInContextEditor
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ desc "Creates a PhraseApp In-Context-Editor initializer for your application."
9
+ class_option :access_token, type: :string, desc: "Your PhraseApp access token", required: true
10
+ class_option :project_id, type: :string, desc: "Your PhraseApp project id", required: true
11
+
12
+ def copy_initializer
13
+ template "phraseapp_in_context_editor.rb", "config/initializers/phraseapp_in_context_editor.rb"
14
+ end
15
+
16
+ def show_readme
17
+ readme "README" if behavior == :invoke
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ ===============================================================================
2
+ Welcome to PhraseApp!
3
+
4
+ PhraseApp is the translation management solution for web and mobile applications. Collaborate with your team, find professional translators and stay on top of the process.
5
+
6
+ Our In-Context Editor offers just that. It provides translators with useful contextual information which improves overall translation quality.
7
+
8
+ Login: https://phraseapp.com/account/login
9
+ Documentation: http://docs.phraseapp.com/guides/in-context-editor/
10
+ ===============================================================================
@@ -0,0 +1,27 @@
1
+ PhraseApp::InContextEditor.configure do |config|
2
+ # Enable or disable the In-Context-Editor in general
3
+ config.enabled = true
4
+
5
+ # Fetch your project id after creating your first project
6
+ # in Translation Center.
7
+ # You can find the project id in your project settings
8
+ # page (https://phraseapp.com/projects)
9
+ config.project_id = "<%= options[:project_id] %>"
10
+
11
+ # You can create and manage access tokens in your profile settings
12
+ # in Translation Center or via the Authorizations API
13
+ # (http://docs.phraseapp.com/api/v2/authorizations/).
14
+ config.access_token = "<%= options[:access_token] %>"
15
+
16
+ # Configure an array of key names that should not be handled
17
+ # by the In-Context-Editor.
18
+ config.ignored_keys = ["number.*", "breadcrumb.*"]
19
+
20
+ # PhraseApp uses decorators to generate a unique identification key
21
+ # in context of your document. However, this might result in conflicts
22
+ # with other libraries (e.g. client-side template engines) that use a similar syntax.
23
+ # If you encounter this problem, you might want to change this decorator pattern.
24
+ # More information: http://docs.phraseapp.com/guides/in-context-editor/configure/
25
+ # config.prefix = "{{__"
26
+ # config.suffix = "__}}"
27
+ end
@@ -0,0 +1,92 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'phraseapp-ruby'
3
+
4
+ module PhraseApp
5
+ module InContextEditor
6
+ autoload :Config, 'phraseapp-in-context-editor-ruby/config'
7
+
8
+ class << self
9
+ def config
10
+ Thread.current[:phraseapp_config] ||= PhraseApp::InContextEditor::Config.new
11
+ end
12
+
13
+ def config=(value)
14
+ Thread.current[:phraseapp_config] = value
15
+ end
16
+
17
+ def backend
18
+ config.backend
19
+ end
20
+
21
+ def suffix
22
+ config.suffix
23
+ end
24
+
25
+ def prefix
26
+ config.prefix
27
+ end
28
+
29
+ def project_id
30
+ config.project_id
31
+ end
32
+
33
+ def access_token
34
+ config.access_token
35
+ end
36
+
37
+ def cache_key_segments_initial
38
+ config.cache_key_segments_initial
39
+ end
40
+
41
+ def cache_lifetime
42
+ config.cache_lifetime
43
+ end
44
+
45
+ def ignored_keys
46
+ config.ignored_keys
47
+ end
48
+
49
+ def enabled=(value)
50
+ config.enabled = value
51
+ end
52
+
53
+ def enabled?
54
+ config.enabled
55
+ end
56
+
57
+ def disabled?
58
+ !config.enabled
59
+ end
60
+
61
+ def js_use_ssl
62
+ config.js_use_ssl
63
+ end
64
+
65
+ def js_host
66
+ config.js_host
67
+ end
68
+
69
+ def api_client
70
+ config.api_client
71
+ end
72
+ end
73
+
74
+ def self.configure
75
+ yield(self.config)
76
+ end
77
+ end
78
+
79
+ autoload :ViewHelpers, 'phraseapp-in-context-editor-ruby/view_helpers'
80
+
81
+ require 'phraseapp-in-context-editor-ruby/version'
82
+ require 'phraseapp-in-context-editor-ruby/engine'
83
+ require 'phraseapp-in-context-editor-ruby/delegate'
84
+ require 'phraseapp-in-context-editor-ruby/backend_service'
85
+ require 'phraseapp-in-context-editor-ruby/view_helpers'
86
+ end
87
+
88
+ # Only load adapters directly if non-rails app, otherwise use engine
89
+ unless defined? Rails
90
+ require 'phraseapp-in-context-editor-ruby/adapters/i18n'
91
+ require 'phraseapp-in-context-editor-ruby/adapters/fast_gettext'
92
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'phraseapp-in-context-editor-ruby/delegate/fast_gettext'
4
+
5
+ module FastGettext
6
+ module Translation
7
+ def __with_phraseapp(*args)
8
+ PhraseApp::InContextEditor::Delegate::FastGettext.new(:_, *args)
9
+ end
10
+ alias_method :__without_phraseapp, :_
11
+ alias_method :_, :__with_phraseapp
12
+
13
+ def n__with_phraseapp(*args)
14
+ PhraseApp::InContextEditor::Delegate::FastGettext.new(:n_, *args)
15
+ end
16
+ alias_method :n__without_phraseapp, :n_
17
+ alias_method :n_, :n__with_phraseapp
18
+
19
+ def s__with_phraseapp(*args)
20
+ PhraseApp::InContextEditor::Delegate::FastGettext.new(:s_, *args)
21
+ end
22
+ alias_method :s__without_phraseapp, :s_
23
+ alias_method :s_, :s__with_phraseapp
24
+ end
25
+ end if defined? FastGettext::Translation
@@ -0,0 +1,12 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module I18n
4
+ class << self
5
+ def translate_with_phraseapp(*args)
6
+ PhraseApp::InContextEditor.backend.translate(*args)
7
+ end
8
+ alias_method :translate_without_phraseapp, :translate
9
+ alias_method :translate, :translate_with_phraseapp
10
+ alias_method :t, :translate
11
+ end
12
+ end if defined?(I18n)
@@ -0,0 +1,41 @@
1
+ module PhraseApp
2
+ module InContextEditor
3
+ class ApiCollection
4
+ def initialize(api_client, action, ids=[], query=nil)
5
+ raise "PhraseApp API client can't handle action #{action}" unless api_client.respond_to?(action)
6
+
7
+ @api_client = api_client
8
+ @action = action
9
+ @ids = ids
10
+ @query = query
11
+ end
12
+
13
+ def collection
14
+ results = []
15
+ page = 1
16
+ per_page = 100
17
+ paginated, err = send_request(page, per_page)
18
+ results << paginated
19
+
20
+ while paginated.size == per_page
21
+ break if page > 100
22
+
23
+ page = page + 1
24
+ paginated, err = send_request(page, per_page)
25
+ results << paginated if paginated.present?
26
+ end
27
+
28
+ results.flatten.uniq
29
+ end
30
+
31
+ private
32
+ def send_request(page, per_page)
33
+ if @query.present?
34
+ @api_client.send(@action, *@ids, page, per_page, @query)
35
+ else
36
+ @api_client.send(@action, *@ids, page, per_page)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'phraseapp-in-context-editor-ruby/api_collection'
2
+
3
+ module PhraseApp
4
+ module InContextEditor
5
+ class ApiWrapper
6
+ def initialize
7
+ @api_client = PhraseApp::InContextEditor.api_client
8
+ end
9
+
10
+ def default_locale
11
+ @default_locale ||= select_default_locale
12
+ end
13
+
14
+ def default_translation(key)
15
+ params = PhraseApp::RequestParams::TranslationsByKeyParams.new
16
+ translations = PhraseApp::InContextEditor::ApiCollection.new(@api_client, "translations_by_key", project_and_key_id(key), params).collection
17
+ return unless translations.present?
18
+
19
+ translations.select{ |translation| translation.locale["id"] == default_locale.id }
20
+ end
21
+
22
+ def keys_with_prefix(prefix)
23
+ params = PhraseApp::RequestParams::KeysListParams.new(q:"#{prefix}*")
24
+ keys_list(params)
25
+ end
26
+
27
+ def keys_by_names(names)
28
+ names = names.join(',')
29
+ params = PhraseApp::RequestParams::KeysListParams.new(:q => "name:#{names}")
30
+ keys_list(params)
31
+ end
32
+
33
+ def keys_list(params)
34
+ PhraseApp::InContextEditor::ApiCollection.new(@api_client, "keys_list", project_id, params).collection
35
+ end
36
+
37
+ def blacklisted_keys
38
+ PhraseApp::InContextEditor::ApiCollection.new(@api_client, "exclude_rules_index", project_id).collection.map{ |rule| rule.name }
39
+ end
40
+
41
+ private
42
+
43
+ def select_default_locale
44
+ locales = PhraseApp::InContextEditor::ApiCollection.new(@api_client, "locales_list", project_id).collection
45
+ return unless locales.present?
46
+
47
+ locales.select{ |loc| loc.default }.first
48
+ end
49
+
50
+ def project_id
51
+ [PhraseApp::InContextEditor.project_id]
52
+ end
53
+
54
+ def project_and_key_id(key)
55
+ project_id << key.id
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,167 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'phraseapp-in-context-editor-ruby/api_collection'
3
+ require 'phraseapp-in-context-editor-ruby/delegate/i18n'
4
+
5
+ module PhraseApp
6
+ module InContextEditor
7
+ class BackendService
8
+
9
+ attr_accessor :blacklisted_keys
10
+
11
+ def initialize(args = {})
12
+ self
13
+ end
14
+
15
+ def translate(*args)
16
+ if to_be_translated_without_phraseapp?(args)
17
+ I18n.translate_without_phraseapp(*args)
18
+ else
19
+ phraseapp_delegate_for(args)
20
+ end
21
+ end
22
+
23
+ protected
24
+ def to_be_translated_without_phraseapp?(args)
25
+ PhraseApp::InContextEditor.disabled? or has_been_given_blacklisted_key?(args) or has_been_given_ignored_key?(args) or has_been_forced_to_resolve_with_phraseapp?(args)
26
+ end
27
+
28
+ def has_been_given_blacklisted_key?(args)
29
+ key = given_key_from_args(args)
30
+ has_blacklist_entry_for_key?(key)
31
+ end
32
+
33
+ def has_been_given_ignored_key?(args)
34
+ key = given_key_from_args(args)
35
+ key_is_ignored?(key)
36
+ end
37
+
38
+ def has_been_forced_to_resolve_with_phraseapp?(args)
39
+ (args.last.is_a?(Hash) and args.last[:resolve] == false)
40
+ end
41
+
42
+ def given_key_from_args(args)
43
+ extract_normalized_key_from_args(args)
44
+ end
45
+
46
+ def has_blacklist_entry_for_key?(key)
47
+ blacklisted_keys.each do |blacklisted_key|
48
+ return true if present?(key.to_s[/\A#{blacklisted_key.gsub("*", ".*")}\Z/])
49
+ end
50
+ false
51
+ end
52
+
53
+ def key_is_ignored?(key)
54
+ PhraseApp::InContextEditor.ignored_keys.each do |ignored_key|
55
+ return true if present?(key.to_s[/\A#{ignored_key.gsub("*", ".*")}\Z/])
56
+ end
57
+ false
58
+ end
59
+
60
+ def blacklisted_keys
61
+ @blacklisted_keys ||= api_wrapper.blacklisted_keys
62
+ end
63
+
64
+ def phraseapp_delegate_for(args)
65
+ key = given_key_from_args(args)
66
+ return nil unless present?(key)
67
+ options = args[1].nil? ? {} : args[1]
68
+ PhraseApp::InContextEditor::Delegate::I18n.new(key, options, args)
69
+ end
70
+
71
+ def extract_normalized_key_from_args(args)
72
+ transformed_args = transform_args(args)
73
+ normalized_key(transformed_args)
74
+ end
75
+
76
+ def transform_args(args)
77
+ duped_args = args.map { |item| (item.is_a?(Symbol) or item.nil?) ? item : item.dup }
78
+ transform_args_based_on_caller(duped_args)
79
+ end
80
+
81
+ def normalized_key(duped_args)
82
+ splitted_args = split_args(duped_args)
83
+ key = I18n::Backend::Flatten.normalize_flat_keys(*splitted_args)
84
+ key.gsub!("..", ".")
85
+ key.gsub!(/^\./, '')
86
+ key
87
+ end
88
+
89
+ def split_args(args)
90
+ options = options_from_args(args)
91
+ key ||= args.shift
92
+ locale = options.delete(:locale) || I18n.locale
93
+ return [locale, key, options[:scope], nil]
94
+ end
95
+
96
+ def options_from_args(args)
97
+ args.last.is_a?(Hash) ? args.pop : {}
98
+ end
99
+
100
+ def transform_args_based_on_caller(args)
101
+ translation_caller = identify_caller
102
+
103
+ if translation_caller and args.first =~ /^\./
104
+ options = options_from_args(args)
105
+
106
+ if not present?(options[:scope]) and present?(translation_caller)
107
+ options[:scope] = translation_caller
108
+ end
109
+
110
+ args.push(options)
111
+ parts = args.first.to_s.split(".").select { |e| not blank?(e) }
112
+ args[0] = parts[0] if parts.size == 1
113
+ end
114
+
115
+ args
116
+ end
117
+
118
+ def identify_caller
119
+ translation_caller = nil
120
+ send(:caller)[0..6].each do |intermediate_caller|
121
+ translation_caller = calling_template(intermediate_caller) unless translation_caller
122
+ end
123
+
124
+ if present?(translation_caller)
125
+ find_lookup_scope(translation_caller)
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ def calling_template(string)
132
+ string.match(/(views)(\/.+)(?>:[0-9]+:in)/)
133
+ end
134
+
135
+ def blank?(str)
136
+ raise "blank?(str) can only be given a String or nil" unless str.is_a?(String) or str.nil?
137
+ str.nil? or str == ''
138
+ end
139
+
140
+ def present?(str)
141
+ raise "present?(str) can only be given a String or nil" unless str.is_a?(String) or str.nil?
142
+ not blank?(str)
143
+ end
144
+
145
+ def find_lookup_scope(caller)
146
+ split_path = caller[2][1..-1].split(".")[0].split("/")
147
+
148
+ template_or_partial = remove_underscore_form_partial(split_path[-1])
149
+ split_path[-1] = template_or_partial
150
+
151
+ split_path.map!(&:to_sym)
152
+ end
153
+
154
+ def remove_underscore_form_partial(template_or_partial)
155
+ if template_or_partial.to_s[0,1] == "_"
156
+ template_or_partial.to_s[1..-1]
157
+ else
158
+ template_or_partial.to_s
159
+ end
160
+ end
161
+
162
+ def api_wrapper
163
+ @api_wrapper ||= PhraseApp::InContextEditor::ApiWrapper.new
164
+ end
165
+ end
166
+ end
167
+ end