Skip to content

Commit d680fb2

Browse files
committed
Initial commit
0 parents  commit d680fb2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+370
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# redmine_custom_messages
2+
3+
Redmineのプラグイン
4+
5+
config/locales/*.ymlで定義されている画面上の文言の訳文を管理画面から変更できるようにします。
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
class CustomMessageSettingsController < ApplicationController
2+
layout 'admin'
3+
self.main_menu = false
4+
before_action :require_admin, :set_custom_message_setting, :set_lang
5+
require_sudo_mode :edit, :update
6+
7+
def edit
8+
@lang = params[:lang].presence || User.current.language
9+
end
10+
11+
def update
12+
original_custom_messages = @setting.custom_messages
13+
languages = (original_custom_messages ? original_custom_messages.keys.map(&:to_s) : [])
14+
15+
if custom_message_setting_params[:custom_messages]
16+
messages = original_custom_messages.merge({@lang => CustomMessageSetting.to_nested_hash(custom_message_setting_params[:custom_messages].to_unsafe_h.to_hash) })
17+
elsif custom_message_setting_params[:custom_messages_yaml]
18+
messages = custom_message_setting_params[:custom_messages_yaml]
19+
else
20+
message = {@lang => {}}
21+
end
22+
if @setting.update_custom_messages(messages)
23+
flash[:notice] = l(:notice_successful_update)
24+
new_custom_messages = @setting.custom_messages
25+
if new_custom_messages.present?
26+
languages += new_custom_messages.keys.map(&:to_s)
27+
CustomMessageSetting.reload_translations!(languages)
28+
end
29+
redirect_to edit_custom_message_settings_path
30+
else
31+
@lang ||= User.current.language
32+
render :edit
33+
end
34+
end
35+
36+
private
37+
38+
def set_custom_message_setting
39+
@setting = CustomMessageSetting.find_or_default
40+
end
41+
42+
def custom_message_setting_params
43+
params.fetch(:settings, {}).permit(:custom_messages_yaml, custom_messages: {})
44+
end
45+
46+
def set_lang
47+
@lang = CustomMessageSetting.find_language(params[:lang]) if params[:lang]
48+
end
49+
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module CustomMessageSettingsHelper
2+
def available_messages(lang)
3+
messages = I18n.backend.translations[lang.to_sym]
4+
if messages.nil?
5+
CustomMessageSetting.reload_translations!([lang])
6+
messages = I18n.backend.translations[lang.to_sym] || {}
7+
end
8+
9+
CustomMessageSetting.flatten_hash(messages)
10+
end
11+
12+
def available_message_options(lang)
13+
[['', '']] +
14+
available_messages(lang).map do |k, v|
15+
["#{k}: #{v}", k]
16+
end
17+
end
18+
19+
def normal_mode_input_fields(setting, lang)
20+
return '' if setting.value[:custom_messages].is_a?(String) || setting.value[:custom_messages].blank?
21+
content = ''
22+
custom_messages_hash = CustomMessageSetting.flatten_hash(setting.custom_messages[lang.to_s] || {})
23+
custom_messages_hash.each do |k, v|
24+
content += content_tag(:p) do
25+
content_tag(:label, k) +
26+
text_field_tag("settings[custom_messages[#{k}]]", v.to_s) +
27+
link_to_function('', '$(this).closest("p").remove();', class: 'icon-only icon-del clear-key-link')
28+
end
29+
end
30+
content.html_safe
31+
end
32+
end
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
class CustomMessageSetting < Setting
2+
3+
before_validation :change_format_to_hash
4+
validate :custom_message_keys_are_available
5+
6+
def self.find_or_default
7+
super('plugin_redmine_message_customize')
8+
end
9+
10+
def custom_messages(lang=nil)
11+
if lang.present?
12+
self.value.dig(:custom_messages, self.class.find_language(lang)) || {}
13+
else
14+
self.value[:custom_messages] || {}
15+
end
16+
end
17+
18+
def custom_messages_to_yaml
19+
if self.custom_messages.blank?
20+
''
21+
elsif self.custom_messages.is_a?(Hash)
22+
YAML.dump(self.custom_messages)
23+
else
24+
self.custom_messages
25+
end
26+
end
27+
28+
def update_custom_messages(messages)
29+
self.value = {custom_messages: (messages.present? ? messages : {})}
30+
self.save
31+
end
32+
33+
def self.available_messages(lang='en')
34+
list = I18n.backend.translations[self.find_language(lang).to_sym] || {}
35+
self.flatten_hash(list)
36+
end
37+
38+
def self.flatten_hash(hash=nil)
39+
hash = self.to_hash unless hash
40+
hash.each_with_object({}) do |(key, value), content|
41+
next self.flatten_hash(value).each do |k, v|
42+
content["#{key}.#{k}".intern] = v
43+
end if value.is_a? Hash
44+
content[key] = value
45+
end
46+
end
47+
48+
def self.to_nested_hash(hash=nil)
49+
hash = self.to_hash unless hash
50+
new_hash = {}
51+
hash.each do |key, value|
52+
h = value
53+
h = YAML.load(value) if value.first == '[' && value.last == ']'
54+
key.to_s.split('.').reverse_each do |k|
55+
h = {k => h}
56+
end
57+
new_hash = new_hash.deep_merge(h)
58+
end
59+
new_hash
60+
end
61+
62+
def self.reload_translations!(languages)
63+
paths = ::I18n.load_path.select {|path| self.find_language(languages).include?(File.basename(path, '.*').to_s)}
64+
I18n.backend.load_translations(paths)
65+
end
66+
67+
def self.find_language(language=nil)
68+
if language.is_a?(Array)
69+
language.select{|l| I18n.available_locales.include?(l.to_s.to_sym)}.map(&:to_s).compact
70+
elsif language.present? && I18n.available_locales.include?(language.to_s.to_sym)
71+
language.to_s
72+
else
73+
nil
74+
end
75+
end
76+
77+
private
78+
79+
def custom_message_keys_are_available
80+
return false if self.value[:custom_messages].is_a?(Hash) == false || self.errors.present?
81+
custom_messages_hash = {}
82+
custom_messages.values.each do |hash|
83+
custom_messages_hash = self.class.flatten_hash(custom_messages_hash.merge(hash))
84+
end
85+
available_keys = self.class.flatten_hash(self.class.available_messages).keys
86+
unavailable_keys = custom_messages_hash.keys.reject{|k|available_keys.include?(k.to_sym)}
87+
if unavailable_keys.present?
88+
self.errors.add(:base, l(:error_unavailable_keys) + "keys: [#{unavailable_keys.join(', ')}]")
89+
return false
90+
end
91+
end
92+
93+
def change_format_to_hash
94+
begin
95+
if self.value[:custom_messages].is_a?(Hash)
96+
YAML.dump(self.value[:custom_messages])
97+
else
98+
self.value = {custom_messages: YAML.load(self.value[:custom_messages])}
99+
end
100+
rescue => e
101+
self.errors.add(:base, e.message)
102+
return false
103+
end
104+
end
105+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<% lang ||= 'en' %>
2+
<div>Add key: <%= select_tag 'select-key', options_for_select(available_message_options(lang)), id: 'key-selector' %></div>
3+
<br>
4+
<div class='tabular'>
5+
<%= normal_mode_input_fields(@setting, lang) %>
6+
</div>
7+
8+
<%= javascript_tag do %>
9+
$(document).ready(function() {
10+
$("#key-selector").select2({
11+
width: '100%',
12+
placeholder: '変更したい文字を検索してください',
13+
});
14+
});
15+
16+
$('#key-selector').on('select2:select', function (e) {
17+
var data = e.params.data;
18+
if($('input[name="settings[custom_messages[' + data.id + ']]"]').length === 0){
19+
$('<p></p>').prependTo($('#edit-custom-messages .tabular'));
20+
$('<label>' + data.id + '</label><br>').appendTo($('#edit-custom-messages .tabular p:first'));
21+
$('<input>').attr({
22+
type: 'text',
23+
value: data.text.replace(/.*: /, ''),
24+
name: 'settings[custom_messages[' + data.id + ']]'
25+
}).appendTo($('#edit-custom-messages .tabular p:first'));
26+
$('<a>').attr({
27+
class: 'icon-only icon-del clear-key-link',
28+
href: '#',
29+
onclick: '$(this).closest("p").remove();; return false;'
30+
}).appendTo($('#edit-custom-messages .tabular p:first'));
31+
$('#key-selector').val('');
32+
}
33+
});
34+
<% end %>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<%= form_tag custom_message_settings_path, method: :patch do %>
2+
<label><%= l(:field_language) %>: </label><%= select_tag 'lang', options_for_select(languages_options, @lang), include_blank: true, data: {remote: true, url: url_for(action: :edit)} %>
3+
<hr>
4+
5+
<div id='edit-custom-messages'>
6+
<%= render 'custom_message_settings/messages', lang: @lang %>
7+
</div>
8+
<%= submit_tag l(:button_save) %>
9+
<% end %>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<%= form_tag custom_message_settings_path, method: :patch do %>
2+
<div class='help-message wiki'>
3+
<p><%= l(:text_help_for_input_format) %></p>
4+
<p><%= l(:text_placeholder_template_case) %></p>
5+
<pre><%= l(:text_placeholder_template) %></pre>
6+
</div>
7+
<%= text_area_tag 'settings[custom_messages_yaml]', @setting.custom_messages_to_yaml, rows: '20', style: 'width: 100%', placeholder: l(:text_placeholder_template) %>
8+
<%= submit_tag l(:button_save) %>
9+
<% end %>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<%= stylesheet_link_tag('custom_messages', plugin: 'redmine_message_customize') %>
2+
<%= stylesheet_link_tag('select2.min', plugin: 'redmine_message_customize') %>
3+
<%= javascript_include_tag('select2.min', plugin: 'redmine_message_customize') %>
4+
<h2><%= l(:label_custom_messages) %></h2>
5+
6+
<% if @setting.errors.any? %>
7+
<%= error_messages_for(@setting) %>
8+
<% end %>
9+
<%= render_tabs (@setting.errors.any? ? [] : [{name: 'normal', partial: 'normal_tab', label: 'label_normal_tab'}]) + [{name: 'yaml', partial: 'yaml_tab', label: 'label_yaml_tab'}] %>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$('#edit-custom-messages').html("<%= j (render 'custom_message_settings/messages', lang: @lang) %>");

assets/javascripts/select2.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)