1
0
Fork 0

IMPROVE: translation feature

Dieser Commit ist enthalten in:
angusmcleod 2021-09-14 11:33:16 +08:00
Ursprung 10fb3ee176
Commit 270d3bccf5
32 geänderte Dateien mit 230 neuen und 143 gelöschten Zeilen

Datei anzeigen

@ -102,8 +102,12 @@ export default Component.extend({
@discourseComputed("isUser", "field", "value")
username(isUser, field, value) {
if (isUser) {return value.username;}
if (field === "username") {return value.value;}
if (isUser) {
return value.username;
}
if (field === "username") {
return value.value;
}
return null;
},
@ -111,7 +115,9 @@ export default Component.extend({
@discourseComputed("username")
userProfileUrl(username) {
if (username) {return `/u/${username}`;}
if (username) {
return `/u/${username}`;
}
return "/";
},

Datei anzeigen

@ -39,7 +39,6 @@ const step = {
id: null,
index: null,
title: null,
key: null,
banner: null,
raw_description: null,
required_data: null,
@ -68,7 +67,6 @@ const field = {
description: null,
property: null,
required: null,
key: null,
type: null,
condition: null,
},

Datei anzeigen

@ -31,14 +31,20 @@ CustomWizardLogs.reopenClass({
result.logs = result.logs.map((item) => {
let map = {};
if (item.date) {map.date = logItem(item, "date");}
if (item.action) {map.action = logItem(item, "action");}
if (item.date) {
map.date = logItem(item, "date");
}
if (item.action) {
map.action = logItem(item, "action");
}
if (item.user) {
map.user = item.user;
} else {
map.user = logItem(item, "username");
}
if (item.message) {map.message = logItem(item, "message");}
if (item.message) {
map.message = logItem(item, "message");
}
return map;
});

Datei anzeigen

@ -126,9 +126,11 @@
</div>
</div>
<div class="setting">
{{#if proSubscribed}}
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}}
@ -136,15 +138,17 @@
</div>
</div>
<div class="setting">
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}}
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div>
</div>
{{/if}}
</div>
{{wizard-links

Datei anzeigen

@ -253,19 +253,6 @@
</div>
{{/if}}
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input
name="key"
value=field.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{#if validations}}
{{wizard-realtime-validations field=field validations=validations}}
{{/if}}

Datei anzeigen

@ -100,19 +100,6 @@
)}}
</div>
</div>
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input
name="key"
value=step.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{/if}}
{{wizard-links

Datei anzeigen

@ -33,7 +33,7 @@ export default ComposerEditor.extend({
lastValidatedAt: "lastValidatedAt",
popupMenuOptions: [],
draftStatus: "null",
replyPlaceholder: alias("field.placeholder"),
replyPlaceholder: alias("field.translatedPlaceholder"),
@on("didInsertElement")
_composerEditorInit() {

Datei anzeigen

@ -16,19 +16,23 @@ export default {
const DEditor = requirejs("discourse/components/d-editor").default;
const { clipboardHelpers } = requirejs("discourse/lib/utilities");
const toMarkdown = requirejs("discourse/lib/to-markdown").default;
const { translatedText } = requirejs(
"discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"
);
FieldComponent.reopen({
classNameBindings: ["field.id"],
@discourseComputed("field.translatedDescription")
cookedDescription(description) {
return cook(description);
},
@discourseComputed("field.type")
textType(fieldType) {
return ["text", "textarea"].includes(fieldType);
},
cookedDescription: function () {
return cook(this.get("field.description"));
}.property("field.description"),
inputComponentName: function () {
const type = this.get("field.type");
const id = this.get("field.id");
@ -57,6 +61,26 @@ export default {
];
FieldModel.reopen({
@discourseComputed("wizardId", "stepId", "id")
i18nKey(wizardId, stepId, id) {
return `${wizardId}.${stepId}.${id}`;
},
@discourseComputed("i18nKey", "label")
translatedLabel(i18nKey, label) {
return translatedText(`${i18nKey}.label`, label);
},
@discourseComputed("i18nKey", "placeholder")
translatedPlaceholder(i18nKey, placeholder) {
return translatedText(`${i18nKey}.placeholder`, placeholder);
},
@discourseComputed("i18nKey", "description")
translatedDescription(i18nKey, description) {
return translatedText(`${i18nKey}.description`, description);
},
check() {
if (this.customCheck) {
return this.customCheck();

Datei anzeigen

@ -22,8 +22,26 @@ export default {
).cook;
const { schedule } = requirejs("@ember/runloop");
const { alias, not } = requirejs("@ember/object/computed");
const { translatedText } = requirejs(
"discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"
);
StepModel.reopen({
@discourseComputed("wizardId", "id")
i18nKey(wizardId, stepId) {
return `${wizardId}.${stepId}`;
},
@discourseComputed("i18nKey", "title")
translatedTitle(i18nKey, title) {
return translatedText(`${i18nKey}.title`, title);
},
@discourseComputed("i18nKey", "description")
translatedDescription(i18nKey, description) {
return translatedText(`${i18nKey}.description`, description);
},
save() {
const wizardId = this.get("wizardId");
const fields = {};
@ -128,13 +146,15 @@ export default {
return index === 0 && !required;
}.property("step.index", "wizard.required"),
cookedTitle: function () {
return cook(this.get("step.title"));
}.property("step.title"),
@discourseComputed("step.translatedTitle")
cookedTitle(title) {
return cook(title);
},
cookedDescription: function () {
return cook(this.get("step.description"));
}.property("step.description"),
@discourseComputed("step.translatedDescription")
cookedDescription(description) {
return cook(description);
},
bannerImage: function () {
const src = this.get("step.banner");

Datei anzeigen

@ -20,13 +20,25 @@ const translationExists = (key) => {
);
};
const getThemeKey = (key) => {
const themeId = getThemeId();
return `theme_translations.${themeId}.${key}`;
};
const translatedText = (key, value) => {
const themeKey = getThemeKey(key);
return translationExists(themeKey) ? I18n.t(themeKey) : value;
};
export { translatedText };
const WizardI18n = (key, params = {}) => {
const themeId = getThemeId();
if (!themeId) {
return I18n.t(key, params);
}
const themeKey = `theme_translations.${themeId}.${key}`;
let themeKey = getThemeKey(key);
if (translationExists(themeKey)) {
return I18n.t(themeKey, params);

Datei anzeigen

@ -41,6 +41,7 @@ CustomWizard.reopenClass({
wizardJson.steps = wizardJson.steps
.map((step) => {
const stepObj = Step.create(step);
stepObj.wizardId = wizardJson.id;
stepObj.fields.sort((a, b) => {
return parseFloat(a.number) - parseFloat(b.number);
@ -57,7 +58,11 @@ CustomWizard.reopenClass({
}
});
stepObj.fields = stepObj.fields.map((f) => WizardField.create(f));
stepObj.fields = stepObj.fields.map((f) => {
f.wizardId = wizardJson.id;
f.stepId = stepObj.id;
return WizardField.create(f);
});
return stepObj;
})

Datei anzeigen

@ -1 +1 @@
{{input id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex autocomplete=autocomplete}}
{{input id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex autocomplete=autocomplete}}

Datei anzeigen

@ -1 +1 @@
{{textarea id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex}}
{{textarea id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex}}

Datei anzeigen

@ -1,5 +1,5 @@
<label for={{field.id}} class="field-label">
{{html-safe field.label}}
{{html-safe field.translatedLabel}}
</label>
{{#if field.image}}

Datei anzeigen

@ -44,8 +44,6 @@ en:
key: "Key"
value: "Value"
profile: "profile"
translation: "Translation"
translation_placeholder: "key"
type: "Type"
none: "Make a selection"
submission_key: 'submission key'
@ -174,10 +172,10 @@ en:
banner: "Banner"
description: "Description"
required_data:
label: "Required"
label: "Required Data"
not_permitted_message: "Message shown when required data not present"
permitted_params:
label: "Params"
label: "Permitted Params"
force_final:
label: "Conditional Final Step"
description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step."

Datei anzeigen

@ -11,7 +11,6 @@ class CustomWizard::Field
:label,
:description,
:image,
:key,
:validations,
:min_length,
:max_length,
@ -36,7 +35,6 @@ class CustomWizard::Field
@value = attrs[:value] || default_value
@description = attrs[:description]
@image = attrs[:image]
@key = attrs[:key]
@validations = attrs[:validations]
@min_length = attrs[:min_length]
@max_length = attrs[:max_length]

Datei anzeigen

@ -9,7 +9,6 @@ class CustomWizard::Step
attr_accessor :index,
:title,
:description,
:key,
:permitted,
:permitted_message,
:fields,

Datei anzeigen

@ -54,10 +54,15 @@ class CustomWizard::TemplateValidator
def self.pro
{
wizard: {},
wizard: {
save_submissions: 'false',
restart_on_revisit: 'true',
},
step: {
condition: 'present',
index: 'conditional'
index: 'conditional',
required_data: 'present',
permitted_params: 'present'
},
field: {
condition: 'present',
@ -87,10 +92,12 @@ class CustomWizard::TemplateValidator
def validate_pro(object, type)
self.class.pro[type].each do |property, pro_type|
is_pro = object[property.to_s].present? && (
pro_type === 'present' ||
(pro_type === 'conditional' && object[property.to_s].is_a?(Hash)) ||
(pro_type.is_a?(Array) && pro_type.include?(object[property.to_s]))
val = object[property.to_s]
is_pro = (val != nil) && (
pro_type === 'present' && val.present? ||
(['true', 'false'].include?(pro_type) && cast_bool(val) == cast_bool(pro_type)) ||
(pro_type === 'conditional' && val.is_a?(Hash)) ||
(pro_type.is_a?(Array) && pro_type.include?(val))
)
if is_pro && !@pro.subscribed?
@ -122,4 +129,8 @@ class CustomWizard::TemplateValidator
errors.add :base, I18n.t("wizard.validation.after_time")
end
end
def cast_bool(val)
ActiveRecord::Type::Boolean.new.cast(val)
end
end

Datei anzeigen

@ -41,13 +41,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
object.value
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.step.id}.fields.#{object.id}".underscore
end
def label
return object.label if object.label.present?
I18n.t("#{object.key || i18n_key}.label", default: '')
I18n.t("#{i18n_key}.label", default: object.label, base_url: Discourse.base_url)
end
def include_label?
@ -55,14 +50,21 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
end
def description
return object.description if object.description.present?
I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
end
def include_description?
description.present?
end
def placeholder
I18n.t("#{i18n_key}.placeholder", default: object.placeholder)
end
def include_placeholder?
placeholder.present?
end
def image
object.image
end
@ -71,15 +73,6 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
object.image.present?
end
def placeholder
return object.placeholder if object.placeholder.present?
I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end
def include_placeholder?
placeholder.present?
end
def file_types
object.file_types
end
@ -122,4 +115,14 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
def preview_template
object.preview_template
end
protected
def i18n_key
@i18n_key ||= "#{object.step.wizard.id}.#{object.step.id}.#{object.id}".underscore
end
def subscribed?
@subscribed ||= CustomWizard::Pro.subscribed?
end
end

Datei anzeigen

@ -9,7 +9,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
:required,
:permitted,
:uncategorized_category_id,
:categories
:categories,
:pro_subscribed
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
@ -60,4 +61,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
def categories
object.categories.map { |c| c.to_h }
end
def pro_subscribed
CustomWizard::Pro.subscribed?
end
end

Datei anzeigen

@ -39,13 +39,8 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
object.previous.present?
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.id}".underscore
end
def title
return PrettyText.cook(object.title) if object.title
PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: ''))
I18n.t("#{i18n_key}.title", default: object.title, base_url: Discourse.base_url)
end
def include_title?
@ -53,8 +48,7 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
end
def description
return object.description if object.description
PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url))
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
end
def include_description?
@ -80,4 +74,14 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
def final
object.final?
end
protected
def i18n_key
@i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore
end
def subscribed?
@subscribed ||= CustomWizard::Pro.subscribed?
end
end

Datei anzeigen

@ -176,7 +176,7 @@ describe CustomWizard::Action do
context "pro actions" do
before do
enable_pro
enable_subscription
end
it '#send_message' do

Datei anzeigen

@ -176,6 +176,7 @@ describe CustomWizard::Builder do
context "restart is enabled" do
before do
enable_subscription
@template[:restart_on_revisit] = true
CustomWizard::Template.save(@template.as_json)
end
@ -204,6 +205,7 @@ describe CustomWizard::Builder do
context 'with required data' do
before do
enable_subscription
@template[:steps][0][:required_data] = required_data_json['required_data']
@template[:steps][0][:required_data_message] = required_data_json['required_data_message']
CustomWizard::Template.save(@template.as_json)
@ -239,6 +241,7 @@ describe CustomWizard::Builder do
context "with permitted params" do
before do
enable_subscription
@template[:steps][0][:permitted_params] = permitted_param_json['permitted_params']
CustomWizard::Template.save(@template.as_json)
end
@ -253,7 +256,7 @@ describe CustomWizard::Builder do
context "with condition" do
before do
enable_pro
enable_subscription
@template[:steps][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json)
end
@ -292,7 +295,7 @@ describe CustomWizard::Builder do
context "with condition" do
before do
enable_pro
enable_subscription
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json)
end
@ -324,6 +327,7 @@ describe CustomWizard::Builder do
context 'save submissions disabled' do
before do
enable_subscription
@template[:save_submissions] = false
CustomWizard::Template.save(@template.as_json)
@wizard = CustomWizard::Builder.new(@template[:id], user).build

Datei anzeigen

@ -217,7 +217,7 @@ describe CustomWizard::CustomField do
context "with a pro subscription" do
before do
enable_pro
enable_subscription
end
it "saves pro field types" do

Datei anzeigen

@ -23,7 +23,6 @@ describe CustomWizard::Field do
expect(field.image).to eq("field_image_url.png")
expect(field.description).to eq("Field description")
expect(field.required).to eq(true)
expect(field.key).to eq("field.locale.key")
expect(field.type).to eq("field_type")
expect(field.content).to eq([])
end

Datei anzeigen

@ -359,7 +359,7 @@ describe CustomWizard::Mapper do
context "with a pro subscription" do
before do
enable_pro
enable_subscription
end
it "treats replaced values as string literals" do

Datei anzeigen

@ -43,47 +43,63 @@ describe CustomWizard::TemplateValidator do
).to eq(false)
end
it "invalidates pro step attributes without a pro subscription" do
context "without subscription" do
it "invalidates subscription wizard attributes" do
template[:save_submissions] = false
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
it "invalidates subscription step attributes" do
template[:steps][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
it "invalidates pro field attributes without a pro subscription" do
it "invalidates subscription field attributes" do
template[:steps][0][:fields][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
it "invalidates pro actions without a pro subscription" do
it "invalidates subscription actions" do
template[:actions] << create_category
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
context "with pro subscription" do
before do
enable_pro
end
it "validates pro step attributes" do
context "with subscription" do
before do
enable_subscription
end
it "validates wizard attributes" do
template[:save_submissions] = false
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
it "validates step attributes" do
template[:steps][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
it "validates pro field attributes" do
it "validates field attributes" do
template[:steps][0][:fields][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
it "validates pro actions" do
it "validates actions" do
template[:actions] << create_category
expect(
CustomWizard::TemplateValidator.new(template).perform

Datei anzeigen

@ -74,7 +74,7 @@ describe "custom field extensions" do
context "pro custom fields" do
before do
enable_pro
enable_subscription
pro_custom_field_json['custom_fields'].each do |field_json|
custom_field = CustomWizard::CustomField.new(nil, field_json)

Datei anzeigen

@ -28,7 +28,7 @@ def authenticate_pro
CustomWizard::ProAuthentication.any_instance.stubs(:active?).returns(true)
end
def enable_pro
def enable_subscription
CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(true)
end

Datei anzeigen

@ -40,7 +40,7 @@ describe "custom field extensions" do
context "with a pro subscription" do
before do
enable_pro
enable_subscription
pro_custom_field_json['custom_fields'].each do |field_json|
custom_field = CustomWizard::CustomField.new(nil, field_json)

Datei anzeigen

@ -121,7 +121,7 @@ describe CustomWizard::StepsController do
context "pro" do
before do
enable_pro
enable_subscription
end
it "raises an error when user cant see the step due to conditions" do

Datei anzeigen

@ -18,7 +18,7 @@ describe CustomWizard::StepSerializer do
each_serializer: described_class,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:title]).to eq("<p>Text</p>")
expect(json_array[0][:title]).to eq("Text")
expect(json_array[0][:description]).to eq("<p>Text inputs!</p>")
expect(json_array[1][:index]).to eq(1)
end
@ -34,6 +34,7 @@ describe CustomWizard::StepSerializer do
context 'with required data' do
before do
enable_subscription
wizard_template['steps'][0]['required_data'] = required_data_json['required_data']
wizard_template['steps'][0]['required_data_message'] = required_data_json['required_data_message']
CustomWizard::Template.save(wizard_template)