kakimasu 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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'