Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions assets/javascripts/discourse/components/show-original-content.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 isTranslated = true;

constructor() {
super(...arguments);
this.isTranslated = !new URLSearchParams(window.location.search).has(
"show"
);
}

@action
async showOriginal() {
const params = new URLSearchParams(window.location.search);
if (this.isTranslated) {
params.append("show", "original");
} else {
params.delete("show");
}
window.location.search = params.toString();
}

get title() {
return this.isTranslated
? "translator.content_translated"
: "translator.content_not_translated";
}

<template>
<div class="discourse-translator_toggle-original">
<DButton
@icon="language"
@title={{this.title}}
class={{concatClass "btn btn-default" (if this.isTranslated "active")}}
@action={{this.showOriginal}}
/>
</div>
</template>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -23,7 +24,33 @@ function initializeTranslation(api) {
);
}

if (currentUser) {
if (
siteSettings.experimental_topic_translation &&
(currentUser || siteSettings.experimental_anon_language_switcher)
) {
api.renderInOutlet("topic-navigation", ShowOriginalContent);
api.decorateCookedElement((cookedElement, helper) => {
if (helper) {
const translatedCooked = helper.getModel().get("translated_cooked");
if (translatedCooked) {
cookedElement.innerHTML = translatedCooked;
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more familiar with ruby so I genuinely am not sure, but in this case do we even want the else for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's just a note to indicate it's a missing feature. Should not cost much.

// this experimental feature does not yet support
// translating individual untranslated posts
}
}
});

api.registerModelTransformer("topic", (topics) => {
topics.forEach((topic) => {
if (topic.translated_title) {
topic.set("fancy_title", topic.translated_title);
}
});
});
}

if (!siteSettings.experimental_topic_translation) {
customizePostMenu(api);
}
}
Expand Down
16 changes: 16 additions & 0 deletions assets/stylesheets/common/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
4 changes: 2 additions & 2 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ en:
discourse_translator: "Discourse Translator"
js:
translator:
view_translation: "View translation"
hide_translation: "Hide translation"
content_not_translated: "Content not translated. Click to translate"
content_translated: "Content is translated. Click to view original"
translated_from: "Translated from %{language} by %{translator}"
translating: "Translating"
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
3 changes: 3 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ discourse_translator:
default: false
client: true
validator: "LanguageSwitcherSettingValidator"
experimental_topic_translation:
default: false
client: true
14 changes: 14 additions & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,18 @@ module ::DiscourseTranslator
add_to_serializer :post, :can_translate do
scope.can_translate?(object)
end

add_to_serializer :post, :translated_cooked do
if !SiteSetting.experimental_topic_translation || scope.request.params["show"] == "original"
return nil
end
object.translation_for(I18n.locale) || nil
end

add_to_serializer :topic_view, :translated_title do
if !SiteSetting.experimental_topic_translation || scope.request.params["show"] == "original"
return nil
end
object.topic.translation_for(I18n.locale) || nil
end
end
36 changes: 36 additions & 0 deletions spec/serializers/post_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,40 @@
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 "does not return translated_cooked when experimental_topic_translation is disabled" do
SiteSetting.experimental_topic_translation = false
expect(serialize_post.translated_cooked).to eq(nil)
end

it "does not return translated_cooked when show=original param is present" do
I18n.locale = "ja"
post.set_translation("ja", "こんにちは")

expect(serialize_post(params: { "show" => "original" }).translated_cooked).to eq(nil)
expect(serialize_post(params: { "show" => "derp" }).translated_cooked).to eq("こんにちは")
end

it "returns translated content based on locale" do
I18n.locale = "ja"
post.set_translation("ja", "こんにちは")
post.set_translation("es", "Hola")
expect(serialize_post.translated_cooked).to eq("こんにちは")
end

it "does not return translated_cooked when plugin is disabled" do
SiteSetting.translator_enabled = false
expect(serialize_post.translated_cooked).to eq(nil)
end
end
end
43 changes: 43 additions & 0 deletions spec/serializers/topic_view_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,47 @@

expect(topic_view.posts.first.association(:content_locale)).to be_loaded
end

describe "#translated_title" do
fab!(:user) { Fabricate(:user, locale: "ja") }
fab!(:topic)

let!(:guardian) { Guardian.new(user) }
let!(:original_title) { "FUS ROH DAAHHH" }
let!(:jap_title) { "フス・ロ・ダ・ア" }

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)
TopicViewSerializer.new(TopicView.new(topic), scope: guardian)
end

it "does not return translated_title when experimental_topic_translation is disabled" do
SiteSetting.experimental_topic_translation = false
topic.set_translation("ja", jap_title)

expect(serialize_topic.translated_title).to eq(nil)
end

it "does not return translated_title when show_original param is present" do
topic.set_translation("ja", jap_title)
expect(serialize_topic(params: { "show" => "original" }).translated_title).to eq(nil)
end

it "does not return translated_title when no translation exists" do
expect(serialize_topic.translated_title).to eq(nil)
end

it "returns translated title when translation exists for current locale" do
topic.set_translation("ja", jap_title)
expect(serialize_topic.translated_title).to eq(jap_title)
end
end
end
57 changes: 57 additions & 0 deletions spec/system/full_page_translation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

RSpec.describe "Full page translation", type: :system do
fab!(:japanese_user) { Fabricate(:user, locale: "ja") }
fab!(:site_local_user) { Fabricate(:user, locale: "en") }
fab!(:author) { Fabricate(:user) }

fab!(:topic) { Fabricate(:topic, title: "Life strategies from The Art of War", user: author) }
fab!(:post_1) do
Fabricate(:post, topic: topic, raw: "The masterpiece isn’t just about military strategy")
end
fab!(:post_2) do
Fabricate(:post, topic: topic, raw: "The greatest victory is that which requires no battle")
end

let(:topic_page) { PageObjects::Pages::Topic.new }

before do
# topic translation setup
topic.set_detected_locale("en")
post_1.set_detected_locale("en")
post_2.set_detected_locale("en")

topic.set_translation("ja", "孫子兵法からの人生戦略")
topic.set_translation("es", "Estrategias de vida de El arte de la guerra")
post_1.set_translation("ja", "傑作は単なる軍事戦略についてではありません")
post_2.set_translation("ja", "最大の勝利は戦いを必要としないものです")
end

context "when the feature is enabled" do
before do
SiteSetting.translator_enabled = true
SiteSetting.allow_user_locale = true
SiteSetting.set_locale_from_cookie = true
SiteSetting.set_locale_from_param = true
SiteSetting.experimental_anon_language_switcher = true
SiteSetting.experimental_topic_translation = true
end

it "shows the correct language based on the selected language and login status" do
visit("/t/#{topic.slug}/#{topic.id}?lang=ja")
expect(topic_page.has_topic_title?("孫子兵法からの人生戦略")).to eq(true)
expect(find(topic_page.post_by_number_selector(1))).to have_content("傑作は単なる軍事戦略についてではありません")

visit("/t/#{topic.id}")
expect(topic_page.has_topic_title?("Life strategies from The Art of War")).to eq(true)
expect(find(topic_page.post_by_number_selector(1))).to have_content(
"The masterpiece isn’t just about military strategy",
)

sign_in(japanese_user)
visit("/")
visit("/t/#{topic.id}")
expect(topic_page.has_topic_title?("孫子兵法からの人生戦略")).to eq(true)
end
end
end
Loading