kakimasu 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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +31 -0
  6. data/.travis.yml +8 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +135 -0
  11. data/Rakefile +6 -0
  12. data/app/controllers/kakimasu/home_controller.rb +18 -0
  13. data/app/controllers/kakimasu/keys_controller.rb +89 -0
  14. data/app/controllers/kakimasu/translations_controller.rb +37 -0
  15. data/app/helpers/counting_helper.rb +11 -0
  16. data/app/helpers/formatting_helper.rb +6 -0
  17. data/app/helpers/grouping_helper.rb +11 -0
  18. data/app/helpers/pagination_helper.rb +15 -0
  19. data/app/helpers/search_key_helper.rb +66 -0
  20. data/app/helpers/search_translations_helper.rb +101 -0
  21. data/app/models/translation.rb +2 -0
  22. data/app/policies/translation_policy.rb +9 -0
  23. data/app/views/kakimasu/home/index.html.erb +35 -0
  24. data/app/views/kakimasu/keys/create.js.erb +1 -0
  25. data/app/views/kakimasu/keys/index.html.erb +183 -0
  26. data/app/views/kakimasu/shared/_navbar.html.erb +15 -0
  27. data/app/views/kakimasu/translations/create.js.erb +1 -0
  28. data/app/views/kakimasu/translations/index.html.erb +168 -0
  29. data/bin/console +14 -0
  30. data/bin/setup +8 -0
  31. data/config/initializers/i18n.rb +58 -0
  32. data/config/initializers/i18n_backend.rb +5 -0
  33. data/config/locales/en.yml +34 -0
  34. data/config/routes.rb +10 -0
  35. data/kakimasu.gemspec +49 -0
  36. data/lib/generators/kakimasu/backup_generator.rb +101 -0
  37. data/lib/generators/kakimasu/policy_generator.rb +18 -0
  38. data/lib/generators/kakimasu/restore_backup_generator.rb +89 -0
  39. data/lib/generators/kakimasu/views_generator.rb +22 -0
  40. data/lib/kakimasu.rb +11 -0
  41. data/lib/kakimasu/engine.rb +3 -0
  42. data/lib/kakimasu/version.rb +3 -0
  43. data/vendor/assets/javascripts/components/activation_button.coffee +10 -0
  44. data/vendor/assets/javascripts/components/modal.coffee +48 -0
  45. data/vendor/assets/javascripts/components/translate.coffee +93 -0
  46. data/vendor/assets/javascripts/components/translation_key_table.coffee +5 -0
  47. data/vendor/assets/javascripts/components/translation_navigation.coffee +13 -0
  48. data/vendor/assets/javascripts/components/translation_panel_table.coffee +5 -0
  49. data/vendor/assets/javascripts/components/translation_percent_chart.coffee +39 -0
  50. data/vendor/assets/javascripts/components/translation_popover.coffee +90 -0
  51. data/vendor/assets/javascripts/jquery/jquery.circliful.min.js +1 -0
  52. data/vendor/assets/javascripts/kakimasu.coffee +18 -0
  53. data/vendor/assets/stylesheets/circliful.scss +38 -0
  54. data/vendor/assets/stylesheets/components/_homepage.scss +17 -0
  55. data/vendor/assets/stylesheets/components/_key_management.scss +53 -0
  56. data/vendor/assets/stylesheets/components/_navbar.scss +52 -0
  57. data/vendor/assets/stylesheets/components/_translate_modal.scss +36 -0
  58. data/vendor/assets/stylesheets/components/_translation_panel.scss +31 -0
  59. data/vendor/assets/stylesheets/components/_translation_popover.scss +31 -0
  60. data/vendor/assets/stylesheets/font-awesome/HELP-US-OUT.txt +7 -0
  61. data/vendor/assets/stylesheets/font-awesome/css/font-awesome.css +2337 -0
  62. data/vendor/assets/stylesheets/font-awesome/css/font-awesome.min.css +4 -0
  63. data/vendor/assets/stylesheets/font-awesome/fonts/FontAwesome.otf +0 -0
  64. data/vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.eot +0 -0
  65. data/vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.svg +2671 -0
  66. data/vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
  67. data/vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff +0 -0
  68. data/vendor/assets/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
  69. data/vendor/assets/stylesheets/font-awesome/less/animated.less +34 -0
  70. data/vendor/assets/stylesheets/font-awesome/less/bordered-pulled.less +25 -0
  71. data/vendor/assets/stylesheets/font-awesome/less/core.less +12 -0
  72. data/vendor/assets/stylesheets/font-awesome/less/fixed-width.less +6 -0
  73. data/vendor/assets/stylesheets/font-awesome/less/font-awesome.less +18 -0
  74. data/vendor/assets/stylesheets/font-awesome/less/icons.less +789 -0
  75. data/vendor/assets/stylesheets/font-awesome/less/larger.less +13 -0
  76. data/vendor/assets/stylesheets/font-awesome/less/list.less +19 -0
  77. data/vendor/assets/stylesheets/font-awesome/less/mixins.less +60 -0
  78. data/vendor/assets/stylesheets/font-awesome/less/path.less +15 -0
  79. data/vendor/assets/stylesheets/font-awesome/less/rotated-flipped.less +20 -0
  80. data/vendor/assets/stylesheets/font-awesome/less/screen-reader.less +5 -0
  81. data/vendor/assets/stylesheets/font-awesome/less/stacked.less +20 -0
  82. data/vendor/assets/stylesheets/font-awesome/less/variables.less +800 -0
  83. data/vendor/assets/stylesheets/font-awesome/scss/_animated.scss +34 -0
  84. data/vendor/assets/stylesheets/font-awesome/scss/_bordered-pulled.scss +25 -0
  85. data/vendor/assets/stylesheets/font-awesome/scss/_core.scss +12 -0
  86. data/vendor/assets/stylesheets/font-awesome/scss/_fixed-width.scss +6 -0
  87. data/vendor/assets/stylesheets/font-awesome/scss/_icons.scss +789 -0
  88. data/vendor/assets/stylesheets/font-awesome/scss/_larger.scss +13 -0
  89. data/vendor/assets/stylesheets/font-awesome/scss/_list.scss +19 -0
  90. data/vendor/assets/stylesheets/font-awesome/scss/_mixins.scss +60 -0
  91. data/vendor/assets/stylesheets/font-awesome/scss/_path.scss +15 -0
  92. data/vendor/assets/stylesheets/font-awesome/scss/_rotated-flipped.scss +20 -0
  93. data/vendor/assets/stylesheets/font-awesome/scss/_screen-reader.scss +5 -0
  94. data/vendor/assets/stylesheets/font-awesome/scss/_stacked.scss +20 -0
  95. data/vendor/assets/stylesheets/font-awesome/scss/_variables.scss +800 -0
  96. data/vendor/assets/stylesheets/font-awesome/scss/font-awesome.scss +18 -0
  97. data/vendor/assets/stylesheets/kakimasu.scss +35 -0
  98. metadata +364 -0
@@ -0,0 +1,18 @@
1
+ module Kakimasu
2
+ module Generators
3
+ class PolicyGenerator < Rails::Generators::Base
4
+ puts "Generating Policy for you ..."
5
+ # Sets root for more compact file copying
6
+ source_root File.expand_path("../../../../app/policies", __FILE__)
7
+
8
+ # Copy policies for ability to edit them
9
+ def copy_policies
10
+ copy_file 'translation_policy.rb', 'app/policies/translations_policy.rb'
11
+ puts ''
12
+ puts '***********************************'
13
+ puts 'Policies was successfully generated!' # Success message
14
+ puts '***********************************'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,89 @@
1
+ module Kakimasu
2
+ module Generators
3
+ class RestoreBackupGenerator < Rails::Generators::Base
4
+ backup_file = '.kakimasu_backup.yml'
5
+
6
+ # Checks if there is backup file
7
+ unless File.file?(backup_file)
8
+ puts '**************************'
9
+ puts "Backup file can't be found"
10
+ puts '**************************'
11
+
12
+ else
13
+ # Reads content of backup file
14
+ backup_file_content = File.read(backup_file)
15
+
16
+ locale = nil # Used to store locale for a temporary time
17
+ key_parts = [] # Used to store parts of the 'lazy lookup' key, to at the end join them in one key
18
+ lazy_lookup = false # Determines if key is lazy lookup
19
+
20
+ # Read backup file by lines
21
+ backup_file_content.each_line do |line|
22
+
23
+ # Every line with translation starts with spaces
24
+ if line.start_with?(' ')
25
+ # Gets level of the key and gets translation
26
+
27
+ # Check if it is first level key
28
+ if line.match /^\s\s[\w]+[:]\s["][^"]+["]/
29
+ # Get translation key and translation from the line
30
+ translation_key = line.scan(/^\s\s([\w]+)[:]\s["][^"]+["]/).first.first
31
+ translation = line.scan(/^\s\s[\w]+[:]\s["]([^"]+)["]/).first.first
32
+
33
+ # empty 'lazy lookup' key parts array because this means that lazy lookup key is done even if there was no translation (That means it has no translation)
34
+ key_parts = []
35
+ lazy_lookup = false
36
+
37
+ # If its not first level translation, then it means that this is 'lazy lookup' translation
38
+ else
39
+ lazy_lookup = true
40
+
41
+ # If line has no translation, just key, then it is part of 'lazy lookup' key
42
+ if line.match /\w+[:]$/
43
+ key_parts.push line.scan(/(\w+)[:]$/).first.first # Gets 'lazy lookup' key part from the line and pushes it into array
44
+
45
+ # If key has translation then it is last part of 'lazy lookup' key
46
+ elsif line.match /\w+[:]\s["]([^"]+)["]/
47
+ # Gets 'lazy lookup' keys last part and gets translation
48
+ key_parts.push line.scan(/(\w+)[:]\s["][^"]+["]/).first.first
49
+ translation = line.scan(/\w+[:]\s["]([^"]+)["]/).first.first
50
+ end
51
+ end
52
+
53
+ # lines with no spaces in start is just for locales or blank lines
54
+ else
55
+ # If line is not meant as blank line, then it's locale and function gets locale from it
56
+ locale = line.partition(':').first unless line.start_with?("\n")
57
+ end
58
+
59
+ # If 'lazy lookup' key then save it as 'lazy lookup' key otherwise save it like not 'lazy lookup key'
60
+ if lazy_lookup && translation
61
+ # Form 'lazy lookup' key
62
+ translation_key = key_parts.join('.')
63
+
64
+ # Store translation
65
+ I18n.backend.store_translations(locale, {translation_key => translation}, escape: false)
66
+
67
+ # Set back variables
68
+ key_parts = []
69
+ translation = nil
70
+ translation_key = nil
71
+
72
+ elsif translation_key && translation
73
+ # Store translation
74
+ I18n.backend.store_translations(locale, {translation_key => translation}, escape: false)
75
+
76
+ # Set back variables
77
+ translation = nil
78
+ translation_key = nil
79
+ end
80
+ end
81
+
82
+ # Message about successfull translation restore from backup file
83
+ puts '***********************************'
84
+ puts 'Translations successfully restored!'
85
+ puts '***********************************'
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,22 @@
1
+ module Kakimasu
2
+ module Generators
3
+ class ViewsGenerator < Rails::Generators::Base
4
+ puts "Creating views for you ..."
5
+ # Sets root for more compact file copying
6
+ source_root File.expand_path("../../../../app/views/kakimasu", __FILE__)
7
+
8
+ # Copy all views to project folder for ability to edit them
9
+ def copy_views
10
+ copy_file "home/index.html.erb", "app/views/kakimasu/home/index.html.erb"
11
+ copy_file "keys/index.html.erb", "app/views/kakimasu/keys/index.html.erb"
12
+ copy_file "translations/index.html.erb", "app/views/kakimasu/translations/index.html.erb"
13
+ copy_file "translations/edit.html.erb", "app/views/kakimasu/translations/edit.html.erb"
14
+ copy_file "shared/_navbar.html.erb", "app/views/kakimasu/shared/_navbar.html.erb"
15
+ puts ''
16
+ puts '***********************************'
17
+ puts 'Views was successfully generated!' # Success message
18
+ puts '***********************************'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'kakimasu/version'
2
+ require 'kakimasu/engine'
3
+
4
+ module Kakimasu
5
+ # Turn of 'translation mode' mechanism
6
+ def set_show_translation_key
7
+ Thread.current[:show_translation_key] = nil
8
+ return unless current_user.try(:admin?)
9
+ Thread.current[:show_translation_key] = (params[:show_translation_key] == 'true').presence
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Kakimasu
2
+ class Engine < Rails::Engine; end
3
+ end
@@ -0,0 +1,3 @@
1
+ module Kakimasu
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,10 @@
1
+ class Kakimasu.ActivationButton
2
+ constructor: (el) ->
3
+ @$el = $(el)
4
+ @delegateEvents()
5
+
6
+ delegateEvents: ->
7
+ @$el.on 'click', '#activate_translation_mode_button', $.proxy(@showAlert, @)
8
+
9
+ showAlert: (e) ->
10
+ alert 'You activated translation mode'
@@ -0,0 +1,48 @@
1
+ class Kakimasu.TranslationModal
2
+ constructor: (el) ->
3
+ @$el = $(el)
4
+ @delegateEvents()
5
+
6
+ delegateEvents: ->
7
+ @$el.on 'click', '.translate-key-button', $.proxy(@fillModal, @)
8
+
9
+ fillModal: (e) ->
10
+ $chosenKey = $(e.currentTarget)
11
+ $('#translation-modal-form #key').val($chosenKey.data('key'))
12
+ $('#translation-modal-form #path').val($chosenKey.data('path'))
13
+
14
+ $('.translation-key').text $chosenKey.data('key')
15
+ $('.translation-key-path').text "( #{$chosenKey.data('path')} )"
16
+
17
+ html = ''
18
+ html += '<p id="lazy-lookup-checkbox-tag">'
19
+ html += '<label for="Unique">Unique</label>'
20
+ html += '<input type="checkbox" name="lazy_lookup" id="lazy_lookup" value="1"'
21
+ if $chosenKey.data('unique')
22
+ html += 'checked="checked"'
23
+ html += '>'
24
+ html += '</p>'
25
+ $('#lazy-lookup-checkbox-tag').remove()
26
+ $('#translation-modal-form #lazy-lookup-checkbox').append html
27
+
28
+ translations = $chosenKey.data('translations')
29
+ locales = $chosenKey.data('locales')
30
+
31
+ for l, index in locales
32
+ translation = translations[index]
33
+ translation = translation.replace /\u0026#39;/g, "'"
34
+ $("#translation-modal-form ##{l}").val('')
35
+ if translation
36
+ unless translation.match /<span.*<\/span>/
37
+ $("#translation-modal-form ##{l}").val(translation)
38
+ $("#translation-modal-form ##{l}").attr('name', l)
39
+ $("#translation-modal-form ##{l}").attr('value', translation)
40
+ else
41
+ if translation.match /<span class='(translation-with-key).*<\/span>/
42
+ match = /<span.*>(.*)<\/span>/i.exec translation
43
+ $("#translation-modal-form ##{l}").val(match[1])
44
+ $("#translation-modal-form ##{l}").attr('name', l)
45
+ $("#translation-modal-form ##{l}").attr('value', match[1])
46
+
47
+ # Closes collapse element if it is open
48
+ $('.collapse').removeClass('in')
@@ -0,0 +1,93 @@
1
+ $(document).on 'click', '.suggested-translation-link', () ->
2
+ form = $(this).parent().parent().parent().parent() # Get form
3
+
4
+ # Get target language, and also check if there is textare or just input
5
+ if $(this).parent().parent().children('.translation-input').children('input').attr('name')
6
+ targetLanguage = $(this).parent().parent().children('.translation-input').children('input').attr('name')
7
+ else
8
+ targetLanguage = $(this).parent().parent().children('.translation-input').children('textarea').attr('name')
9
+
10
+ numberOfLanguages = form.children('.form-group').length # Get number of all languages
11
+
12
+ # Will be used if there is no translation for the key in other languages but there is translation for the key in this language
13
+ hasTranslation = false
14
+
15
+ # Get source language and translatable string (first language with translation which is not target language)
16
+ for i in [0...numberOfLanguages]
17
+ formGroup = form.children('.form-group').eq(i)
18
+ # Determines if there ir textarea or input field
19
+ if formGroup.children().eq(0).children('.translation-input').children('textarea').val()
20
+ value = formGroup.children().eq(0).children('.translation-input').children('textarea').val()
21
+ language = formGroup.children().eq(0).children('.translation-input').children('textarea').attr('name')
22
+ else
23
+ value = formGroup.children().eq(0).children('.translation-input').children('input').val()
24
+ language = formGroup.children().eq(0).children('.translation-input').children('input').attr('name')
25
+
26
+ # If there is translation for key in language that is not target language then it will be translatable string
27
+ if value && language != targetLanguage
28
+ sourceLanguage = language
29
+ translatableString = value
30
+ break
31
+ # In case if there is no other language with translation and this language has translation then this will be displayed translation
32
+ if value && language == targetLanguage
33
+ hasTranslation = true
34
+ result = value
35
+
36
+ # Alert user if there is nothing to get translation from
37
+ alert 'There is no word to translate. Please fill at least one translation.' unless translatableString || hasTranslation
38
+
39
+ # Check for internet connection, if there is no connection then alert user
40
+ unless navigator.onLine
41
+ alert 'Unable to connect google translate service. Please make sure that you are connected to the internet!'
42
+ else
43
+ if translatableString
44
+ # Regular expression for searching punctuation marks
45
+ regex = /[^\.!\?]+[\.!\?]+/g
46
+
47
+ # If translatable string includes many sentences then every sentence should be translated independently
48
+ if regex.test(translatableString)
49
+ sentences = translatableString.match( regex )
50
+
51
+ # Array for translation storage
52
+ translation = []
53
+
54
+ # Translate every sentence independently
55
+ for sentence in sentences
56
+ # Google translate API url
57
+ url = "https://translate.googleapis.com/translate_a/single?client=gtx"
58
+ url += "&sl=" + sourceLanguage
59
+ url += "&tl=" + targetLanguage
60
+ url += "&dt=t&q=" + encodeURI(sentence)
61
+
62
+ # HTTP GET request for translation
63
+ xmlHttp = new XMLHttpRequest()
64
+ xmlHttp.open( "GET", url, false )
65
+ xmlHttp.send( null )
66
+
67
+ # Parse API response to JSON
68
+ response = JSON.parse(xmlHttp.responseText)
69
+
70
+ # Get suggested translation
71
+ translation.push response[0][0][0]
72
+
73
+ result = translation.join(' ')
74
+
75
+ # If translatable string is just one sentence then it can be translated directly
76
+ else
77
+ # Google translate API url
78
+ url = "https://translate.googleapis.com/translate_a/single?client=gtx"
79
+ url += "&sl=" + sourceLanguage
80
+ url += "&tl=" + targetLanguage
81
+ url += "&dt=t&q=" + encodeURI(translatableString)
82
+
83
+ # HTTP GET request for translation
84
+ xmlHttp = new XMLHttpRequest()
85
+ xmlHttp.open( "GET", url, false )
86
+ xmlHttp.send( null )
87
+
88
+ # Get translation
89
+ response = JSON.parse(xmlHttp.responseText)
90
+ result = response[0][0][0]
91
+
92
+ # Show suggested translation to user
93
+ $(this).children('.suggested-translation').children('p').text result
@@ -0,0 +1,5 @@
1
+ class Kakimasu.TranslationKeyTable
2
+ constructor: (el) ->
3
+ $('.translation-key-table tbody .translation_missing')
4
+ .parent()
5
+ .css 'background-color', '#F8CECC'
@@ -0,0 +1,13 @@
1
+ class Kakimasu.TranslationNavigation
2
+ constructor: (el) ->
3
+ @$el = $(el)
4
+ @delegateEvents()
5
+
6
+ delegateEvents: ->
7
+ @$el.on 'click', 'li', $.proxy(@changeActiveListItem, @)
8
+
9
+ changeActiveListItem: (e) ->
10
+ $chosenItem = $(e.currentTarget)
11
+
12
+ $('.kakimasu-navigation .active').removeClass('active')
13
+ $chosenItem.addClass('active')
@@ -0,0 +1,5 @@
1
+ class Kakimasu.TranslationPanelTable
2
+ constructor: (el) ->
3
+ $('.translations-table tbody .translation_missing')
4
+ .parent()
5
+ .css 'background-color', '#F8CECC'
@@ -0,0 +1,39 @@
1
+ class Kakimasu.TranslationPercentChart
2
+ constructor: (el) ->
3
+ # Get translation percentage from the view
4
+ percentage = $('.translation-percentage').data('value')
5
+
6
+ # Set chart and text color
7
+ if percentage <= 25
8
+ chartColor = '#8B0000'
9
+ textColor = '#4D0000'
10
+ else if percentage <= 50 && percentage > 25
11
+ chartColor = '#FF0000'
12
+ textColor = '#B30000'
13
+ else if percentage <=75 && percentage > 50
14
+ chartColor = '#FFA500'
15
+ textColor = '#B37400'
16
+ else if percentage <= 90 && percentage > 75
17
+ chartColor = '#808000'
18
+ textColor = '#808000'
19
+ else if percentage <= 99 && percentage > 90
20
+ chartColor = '#6B8E23'
21
+ textColor = '#3D5214'
22
+ else
23
+ chartColor = '#008000'
24
+ textColor = '#003300'
25
+
26
+ # translation percentage circle diagram
27
+ $('#translation-percentage-circle').circliful
28
+ animation: 1
29
+ animationStep: 2
30
+ start: 2
31
+ showPercent: 1
32
+ percent: percentage
33
+ backgroundColor: '#666666'
34
+ foregroundColor: chartColor
35
+ fontColor: textColor
36
+ multiPercentage: 1
37
+ foregroundBorderWidth: 15
38
+ backgroundBorderWidth: 1
39
+ percentageY: 105
@@ -0,0 +1,90 @@
1
+ class Kakimasu.TranslationPopover
2
+ constructor: (el) ->
3
+ @$el = $(el)
4
+ @delegateEvents()
5
+
6
+ delegateEvents: ->
7
+ @$el.on 'mouseenter', $.proxy(@showPopover, @)
8
+
9
+ showPopover: (e) ->
10
+ $translation = $(e.currentTarget)
11
+
12
+ token = $('meta[name="csrf-token"]').attr('content')
13
+ url_params = window.location.search
14
+ key = $translation.data('content')
15
+ translations = $translation.data('translations')
16
+
17
+ #determine available locales
18
+ locales = $translation.data('locales')
19
+ locales = locales.split /\W+/
20
+ locales.shift()
21
+ locales.pop()
22
+
23
+ html = '<div class="popover translation-popover">'
24
+ html += '<div class="close">'
25
+ html += '<p>&times;</p>'
26
+ html += '</div>'
27
+ html += '<form action="/kakimasu/translations'
28
+ html += url_params
29
+ html += '" data-remote="true" method="post">'
30
+ html += '<input type="hidden" name="authenticity_token" '
31
+ html += 'value='
32
+ html += token
33
+ html += '>'
34
+ html += '<input type="hidden" name="key" id="translation-key" value='
35
+ html += key
36
+ html += '>'
37
+ html += '<div class="translation-key">'
38
+ html += key
39
+ html += '</div>'
40
+ for l, index in locales
41
+ html += '<div class="form-group">'
42
+ html += '<div class="row">'
43
+ html += '<div class="col-xs-2">'
44
+ html += "<label for='locale'>#{l}</label>"
45
+ html += '</div>'
46
+ html += '<div class="col-xs-10 translation-input">'
47
+ field_value = translations[index].replace /"/g, '&quot;'
48
+ field_value = '"' + field_value + '"'
49
+ html += "<input class='form-control' type='text' name='#{l}' id='value' value=#{field_value} >"
50
+ html += '</div>'
51
+ html += '<div class="col-xs-10 col-xs-offset-2">'
52
+ html += "<div class='suggested-translation-link'>"
53
+ html += "<a href='#popover-translation-#{index}' data-toggle='collapse'>"
54
+ html += 'suggested translations'
55
+ html += '</a>'
56
+ html += "<div id='popover-translation-#{index}' class='collapse suggested-translation'>"
57
+ html += '<p></p>'
58
+ html += '</div>'
59
+ html += '</div>'
60
+ html += '</div>'
61
+ html += '</div>'
62
+ html += '</div>'
63
+ if $translation.data('unique')
64
+ html += '<p>'
65
+ html += '<label for="Unique">Unique</label>'
66
+ html += '<input type="checkbox" name="lazy_lookup" id="lazy_lookup" value="1"'
67
+ html += 'checked="checked"'
68
+ html += 'disabled'
69
+ html += '>'
70
+ html += '</p>'
71
+ html += '<p class="text-right">'
72
+ html += '<input type="submit" name="commit" value="Save" class="btn btn-primary">'
73
+ html += '</p>'
74
+ html += '</form>'
75
+ html += '</div>'
76
+
77
+ $translation.popover
78
+ container: 'body'
79
+ template: html
80
+
81
+ $('.popover').remove()
82
+ $('.translation-with-key').css 'background-color', '#FFCCCC'
83
+ $('.translation_missing').css 'background-color', '#FF6666'
84
+ $translation.popover('show')
85
+ $translation.css 'background-color', '#990000'
86
+
87
+ $('.close').on 'click', ->
88
+ $(this).closest('.popover').hide()
89
+ $('.translation-with-key').css 'background-color', '#FFCCCC'
90
+ $('.translation_missing').css 'background-color', '#FF6666'