diff --git a/assets/javascripts/discourse/components/show-original-content.gjs b/assets/javascripts/discourse/components/show-original-content.gjs new file mode 100644 index 0000000..e62970a --- /dev/null +++ b/assets/javascripts/discourse/components/show-original-content.gjs @@ -0,0 +1,39 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import concatClass from "discourse/helpers/concat-class"; + +export default class ShowOriginalContent extends Component { + @service router; + @tracked active = true; + + constructor() { + super(...arguments); + this.active = !new URLSearchParams(window.location.search).has("show"); + } + + @action + async showOriginal() { + this.active = !this.active; + window.location.search = this.active ? "" : `show=original`; + } + + get title() { + return this.active + ? "translator.hide_translation" + : "translator.show_translation"; + } + + +} diff --git a/assets/javascripts/discourse/initializers/extend-for-translate-button.js b/assets/javascripts/discourse/initializers/extend-for-translate-button.js index 44b75c4..edfd206 100644 --- a/assets/javascripts/discourse/initializers/extend-for-translate-button.js +++ b/assets/javascripts/discourse/initializers/extend-for-translate-button.js @@ -5,6 +5,7 @@ import { withPluginApi } from "discourse/lib/plugin-api"; import { i18n } from "discourse-i18n"; import LanguageSwitcher from "../components/language-switcher"; import ToggleTranslationButton from "../components/post-menu/toggle-translation-button"; +import ShowOriginalContent from "../components/show-original-content"; import TranslatedPost from "../components/translated-post"; function initializeTranslation(api) { @@ -22,8 +23,9 @@ function initializeTranslation(api) { { before: ["search"] } ); } - - if (currentUser) { + if (siteSettings.experimental_topic_translation) { + api.renderInOutlet("topic-navigation", ShowOriginalContent); + } else { customizePostMenu(api); } } diff --git a/assets/stylesheets/common/common.scss b/assets/stylesheets/common/common.scss index 202aec1..5fe5938 100644 --- a/assets/stylesheets/common/common.scss +++ b/assets/stylesheets/common/common.scss @@ -2,3 +2,19 @@ .fk-d-menu__inner-content { max-height: 50vh; } + +.topic-navigation.with-timeline .discourse-translator_toggle-original { + margin-bottom: 0.5em; +} + +.topic-navigation.with-topic-progress + .discourse-translator_toggle-original + button { + height: 100%; +} + +.discourse-translator_toggle-original { + button.active svg { + color: var(--tertiary); + } +} diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index ff4ceb4..e423052 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -20,6 +20,7 @@ en: experimental_anon_language_switcher: "Enable experimental language switcher for anonymous users. This will allow anonymous users to switch between translated versions of Discourse and user-contributed content in topics." errors: set_locale_cookie_requirements: "The experimental language switcher for anonymous users requires the `set locale from cookie` site setting to be enabled." + experimental_topic_translation: "Enable experimental topic translation feature. This replaces existing post in-line translation with a button that allows users to translate the entire topic." translator: failed: "The translator is unable to translate this content (%{source_locale}) to the default language of this site (%{target_locale})." not_supported: "This language is not supported by the translator." diff --git a/config/settings.yml b/config/settings.yml index b17ece4..bb64319 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -106,3 +106,6 @@ discourse_translator: default: false client: true validator: "LanguageSwitcherSettingValidator" + experimental_topic_translation: + default: false + client: true diff --git a/lib/discourse_translator/translator_helper.rb b/lib/discourse_translator/translator_helper.rb new file mode 100644 index 0000000..cb47b35 --- /dev/null +++ b/lib/discourse_translator/translator_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module DiscourseTranslator + module TranslatorHelper + def self.translated_value(original_value, model, scope) + return original_value if !SiteSetting.experimental_topic_translation + return original_value if scope.request.params["show"] == "original" + + translated = model.custom_fields[TRANSLATED_CUSTOM_FIELD] + return original_value if (translated.blank? || translated[I18n.locale].blank?) + + translated[I18n.locale] + end + end +end diff --git a/plugin.rb b/plugin.rb index f74dc22..5e7f28a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -47,4 +47,13 @@ module ::DiscourseTranslator add_to_serializer :post, :can_translate do scope.can_translate?(object) end + + add_to_serializer :post, :cooked, respect_plugin_enabled: false do + return super() if cooked_hidden + DiscourseTranslator::TranslatorHelper.translated_value(super(), object, scope) + end + + add_to_serializer :basic_topic, :fancy_title do + DiscourseTranslator::TranslatorHelper.translated_value(object.fancy_title, object, scope) + end end diff --git a/spec/serializers/basic_topic_serializer_spec.rb b/spec/serializers/basic_topic_serializer_spec.rb new file mode 100644 index 0000000..b324d71 --- /dev/null +++ b/spec/serializers/basic_topic_serializer_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe BasicTopicSerializer do + let!(:guardian) { Guardian.new(user) } + let!(:original_title) { "FUS ROH DAAHHH" } + let!(:jap_title) { "フス・ロ・ダ・ア" } + + describe "#fancy_title" do + fab!(:user) { Fabricate(:user, locale: "ja") } + fab!(:topic) + + before do + topic.title = original_title + SiteSetting.experimental_topic_translation = true + I18n.locale = "ja" + end + + def serialize_topic(guardian_user: user, params: {}) + env = { "action_dispatch.request.parameters" => params, "REQUEST_METHOD" => "GET" } + request = ActionDispatch::Request.new(env) + guardian = Guardian.new(guardian_user, request) + BasicTopicSerializer.new(topic, scope: guardian) + end + + it "returns original fancy_title when experimental_topic_translation is disabled" do + SiteSetting.experimental_topic_translation = false + topic.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { "ja" => jap_title } + + expect(serialize_topic.fancy_title).to eq(original_title) + end + + it "returns original fancy_title when show_original param is present" do + topic.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { "ja" => jap_title } + expect(serialize_topic(params: { "show" => "original" }).fancy_title).to eq(original_title) + end + + it "returns original fancy_title when no translation exists" do + expect(serialize_topic.fancy_title).to eq(original_title) + end + + it "returns original fancy_title when translation is blank for current locale" do + topic.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { "ja" => "" } + expect(serialize_topic.fancy_title).to eq(original_title) + end + + it "returns translated title when translation exists for current locale" do + topic.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { "ja" => jap_title } + expect(serialize_topic.fancy_title).to eq(jap_title) + end + end +end diff --git a/spec/serializers/post_serializer_spec.rb b/spec/serializers/post_serializer_spec.rb index 04d46b4..e00a8b1 100644 --- a/spec/serializers/post_serializer_spec.rb +++ b/spec/serializers/post_serializer_spec.rb @@ -80,4 +80,44 @@ end end end + + describe "#cooked" do + def serialize_post(guardian_user: user, params: {}) + env = { "action_dispatch.request.parameters" => params, "REQUEST_METHOD" => "GET" } + request = ActionDispatch::Request.new(env) + guardian = Guardian.new(guardian_user, request) + PostSerializer.new(post, scope: guardian) + end + + before { SiteSetting.experimental_topic_translation = true } + + it "returns original cooked when experimental_topic_translation is disabled" do + SiteSetting.experimental_topic_translation = false + original_cooked = post.cooked + expect(serialize_post.cooked).to eq(original_cooked) + end + + it "returns original cooked when show=original param is present" do + original_cooked = post.cooked + I18n.locale = "ja" + post.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { "ja" => "こんにちは" } + expect(serialize_post(params: { "show" => "original" }).cooked).to eq(original_cooked) + expect(serialize_post(params: { "show" => "derp" }).cooked).to eq("こんにちは") + end + + it "returns translated content based on locale" do + I18n.locale = "ja" + post.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] = { + "ja" => "こんにちは", + "es" => "Hola", + } + expect(serialize_post.cooked).to eq("こんにちは") + end + + it "returns original cooked when plugin is disabled" do + SiteSetting.translator_enabled = false + original_cooked = post.cooked + expect(serialize_post.cooked).to eq(original_cooked) + end + end end