Skip to content

Commit 6e8b183

Browse files
authored
FEATURE: Allow untranslated posts in inline-translation mode to be manually translated (#230)
Currently the 🌐 only appears in dual-text mode (not inline translation mode) when the `experimental_topic_translation` feature is disabled. This means we are unable to translate old posts when in `experimental_topic_translation`. This PR allows 🌐 to show up when `experimental_topic_translation` is enabled, and does not show the dual-text translation below (the old method) but replaces the content inline instead. In addition in this PR, when `experimental_topic_translation` is enabled, it takes the response from /translate and updates the post cooked and topic title for inline replacement. (in other scenarios, translation updates are done by messagebus) (also backfills many untested js files)
1 parent cf11bde commit 6e8b183

File tree

7 files changed

+253
-30
lines changed

7 files changed

+253
-30
lines changed

assets/javascripts/discourse/components/post-menu/toggle-translation-button.gjs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export default class ToggleTranslationButton extends Component {
2121
return this.args.post.isTranslated;
2222
}
2323

24+
get showButton() {
25+
return this.args.post.can_translate;
26+
}
27+
2428
get title() {
2529
if (this.isTranslating) {
2630
return "translator.translating";
@@ -63,17 +67,19 @@ export default class ToggleTranslationButton extends Component {
6367
}
6468

6569
<template>
66-
<DButton
67-
class={{concatClass
68-
"post-action-menu__translate"
69-
(if this.isTranslated "translated")
70-
}}
71-
...attributes
72-
@action={{this.toggleTranslation}}
73-
@disabled={{this.isTranslating}}
74-
@icon="globe"
75-
@label={{if @showLabel this.title}}
76-
@title={{this.title}}
77-
/>
70+
{{#if this.showButton}}
71+
<DButton
72+
class={{concatClass
73+
"post-action-menu__translate"
74+
(if this.isTranslated "translated")
75+
}}
76+
...attributes
77+
@action={{this.toggleTranslation}}
78+
@disabled={{this.isTranslating}}
79+
@icon="globe"
80+
@label={{if @showLabel this.title}}
81+
@title={{this.title}}
82+
/>
83+
{{/if}}
7884
</template>
7985
}

assets/javascripts/discourse/components/translated-post.gjs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,35 @@ export default class TranslatedPost extends Component {
3131
return this.post.translatedTitle;
3232
}
3333

34+
get showTranslation() {
35+
return !this.siteSettings.experimental_topic_translation;
36+
}
37+
3438
<template>
3539
<div class="post-translation">
3640
<ConditionalLoadingSpinner
3741
class="post-translation"
3842
@condition={{this.loading}}
3943
@size="small"
4044
>
41-
<hr />
42-
{{#if this.translatedTitle}}
43-
<div class="topic-attribution">
44-
{{this.translatedTitle}}
45+
{{#if this.showTranslation}}
46+
<hr />
47+
{{#if this.translatedTitle}}
48+
<div class="topic-attribution">
49+
{{this.translatedTitle}}
50+
</div>
51+
{{/if}}
52+
<div class="post-attribution">
53+
{{i18n
54+
"translator.translated_from"
55+
language=this.post.detectedLang
56+
translator=this.siteSettings.translator
57+
}}
58+
</div>
59+
<div class="cooked">
60+
{{htmlSafe this.post.translatedText}}
4561
</div>
4662
{{/if}}
47-
<div class="post-attribution">
48-
{{i18n
49-
"translator.translated_from"
50-
language=this.post.detectedLang
51-
translator=this.siteSettings.translator
52-
}}
53-
</div>
54-
<div class="cooked">
55-
{{htmlSafe this.post.translatedText}}
56-
</div>
5763
</ConditionalLoadingSpinner>
5864
</div>
5965
</template>

assets/javascripts/discourse/initializers/extend-for-translate-button.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ function initializeTranslation(api) {
3131
api.renderInOutlet("topic-navigation", ShowOriginalContent);
3232
}
3333

34-
if (!siteSettings.experimental_topic_translation) {
35-
customizePostMenu(api);
36-
}
34+
customizePostMenu(api);
3735
}
3836

3937
function customizePostMenu(api, container) {

assets/javascripts/discourse/services/translator.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import Service from "@ember/service";
1+
import Service, { service } from "@ember/service";
22
import { ajax } from "discourse/lib/ajax";
33

44
export default class TranslatorService extends Service {
5+
@service siteSettings;
6+
@service appEvents;
7+
@service documentTitle;
8+
59
async translatePost(post) {
610
const response = await ajax("/translator/translate", {
711
type: "POST",
@@ -11,6 +15,16 @@ export default class TranslatorService extends Service {
1115
post.detectedLang = response.detected_lang;
1216
post.translatedText = response.translation;
1317
post.translatedTitle = response.title_translation;
18+
if (this.siteSettings.experimental_topic_translation) {
19+
if (post.post_number === 1) {
20+
post.topic.set("fancy_title", response.title_translation);
21+
this.appEvents.trigger("header:update-topic", post.topic);
22+
this.documentTitle.setTitle(response.title_translation);
23+
}
24+
post.set("cooked", response.translation);
25+
post.set("can_translate", false);
26+
this.appEvents.trigger("post-stream:refresh", { id: post.id });
27+
}
1428
}
1529

1630
clearPostTranslation(post) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { render } from "@ember/test-helpers";
2+
import { hbs } from "ember-cli-htmlbars";
3+
import { module, test } from "qunit";
4+
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
5+
6+
module("Integration | Component | toggle-translation-button", function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test("doesn't render when post cannot be translated", async function (assert) {
10+
this.set("post", { can_translate: false });
11+
12+
await render(hbs`
13+
<PostMenu::ToggleTranslationButton @post={{this.post}} />
14+
`);
15+
16+
assert.dom("button").doesNotExist();
17+
});
18+
19+
test("renders translation button with correct states", async function (assert) {
20+
const post = {
21+
can_translate: true,
22+
isTranslated: false,
23+
isTranslating: false,
24+
};
25+
26+
this.set("post", post);
27+
28+
await render(hbs`
29+
<PostMenu::ToggleTranslationButton @post={{this.post}} @showLabel={{true}} />
30+
`);
31+
32+
assert.dom("button").exists();
33+
assert.dom("button").hasText("View translation");
34+
assert.dom("button").doesNotHaveClass("translated");
35+
36+
post.isTranslating = true;
37+
await render(hbs`
38+
<PostMenu::ToggleTranslationButton @post={{this.post}} @showLabel={{true}} />
39+
`);
40+
assert.dom("button").hasAttribute("disabled");
41+
assert.dom("button").hasText("Translating");
42+
43+
post.isTranslating = false;
44+
post.isTranslated = true;
45+
await render(hbs`
46+
<PostMenu::ToggleTranslationButton @post={{this.post}} @showLabel={{true}} />
47+
`);
48+
assert.dom("button").hasClass("translated");
49+
assert.dom("button").hasText("Hide translation");
50+
});
51+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { render } from "@ember/test-helpers";
2+
import { hbs } from "ember-cli-htmlbars";
3+
import { module, test } from "qunit";
4+
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
5+
6+
module("Integration | Component | translated-post", function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test("renders translation when post is translated", async function (assert) {
10+
this.set("outletArgs", {
11+
post: {
12+
isTranslated: true,
13+
isTranslating: false,
14+
translatedText: "こんにちは",
15+
translatedTitle: "良い一日",
16+
detectedLang: "ja",
17+
},
18+
});
19+
20+
this.siteSettings.experimental_topic_translation = false;
21+
this.siteSettings.translator = "Google";
22+
23+
await render(hbs`
24+
<TranslatedPost @outletArgs={{this.outletArgs}} />
25+
`);
26+
27+
assert.dom(".post-translation").exists();
28+
assert.dom(".topic-attribution").hasText("良い一日");
29+
assert.dom(".post-attribution").hasText("Translated from ja by Google");
30+
assert.dom(".cooked").hasText("こんにちは");
31+
});
32+
33+
test("hides translation when experimental_topic_translation is enabled", async function (assert) {
34+
this.set("outletArgs", {
35+
post: {
36+
isTranslated: true,
37+
isTranslating: false,
38+
translatedText: "Bonjour monde",
39+
},
40+
});
41+
42+
this.siteSettings.experimental_topic_translation = true;
43+
44+
await render(hbs`
45+
<TranslatedPost @outletArgs={{this.outletArgs}} />
46+
`);
47+
48+
assert.dom(".topic-attribution").doesNotExist();
49+
assert.dom(".post-attribution").doesNotExist();
50+
});
51+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { setupTest } from "ember-qunit";
2+
import { module, test } from "qunit";
3+
import pretender, { response } from "discourse/tests/helpers/create-pretender";
4+
5+
module("Unit | Service | translator", function (hooks) {
6+
setupTest(hooks);
7+
8+
test("translatePost - standard translation", async function (assert) {
9+
const service = this.owner.lookup("service:translator");
10+
11+
pretender.post("/translator/translate", () => {
12+
return response({
13+
detected_lang: "ja",
14+
translation: "I am a cat",
15+
title_translation: "Surprise!",
16+
});
17+
});
18+
19+
const post = {
20+
id: 1,
21+
post_number: 2,
22+
};
23+
24+
await service.translatePost(post);
25+
26+
assert.strictEqual(post.detectedLang, "ja");
27+
assert.strictEqual(post.translatedText, "I am a cat");
28+
assert.strictEqual(post.translatedTitle, "Surprise!");
29+
});
30+
31+
test("translatePost - with experimental translation for first post", async function (assert) {
32+
const service = this.owner.lookup("service:translator");
33+
34+
service.siteSettings.experimental_topic_translation = true;
35+
36+
let headerUpdateCalled = false;
37+
let postStreamRefreshCalled = false;
38+
let titleSet = null;
39+
40+
service.appEvents.on(
41+
"header:update-topic",
42+
() => (headerUpdateCalled = true)
43+
);
44+
service.appEvents.on(
45+
"post-stream:refresh",
46+
() => (postStreamRefreshCalled = true)
47+
);
48+
service.documentTitle.setTitle = (title) => (titleSet = title);
49+
50+
pretender.post("/translator/translate", () => {
51+
return response({
52+
detected_lang: "ja",
53+
translation: "I am a cat",
54+
title_translation: "Surprise!",
55+
});
56+
});
57+
58+
const topic = {
59+
set: function (key, value) {
60+
this[key] = value;
61+
},
62+
};
63+
const post = {
64+
id: 1,
65+
post_number: 1,
66+
topic,
67+
set: function (key, value) {
68+
this[key] = value;
69+
},
70+
};
71+
72+
await service.translatePost(post);
73+
74+
assert.true(headerUpdateCalled);
75+
assert.true(postStreamRefreshCalled);
76+
assert.strictEqual(titleSet, "Surprise!");
77+
assert.strictEqual(post.cooked, "I am a cat");
78+
assert.false(post.can_translate);
79+
assert.strictEqual(topic.fancy_title, "Surprise!");
80+
});
81+
82+
test("clearPostTranslation", function (assert) {
83+
const service = this.owner.lookup("service:translator");
84+
85+
const post = {
86+
detectedLang: "ja",
87+
translatedText: "Hello",
88+
translatedTitle: "Title",
89+
};
90+
91+
service.clearPostTranslation(post);
92+
93+
assert.strictEqual(post.detectedLang, null);
94+
assert.strictEqual(post.translatedText, null);
95+
assert.strictEqual(post.translatedTitle, null);
96+
});
97+
});

0 commit comments

Comments
 (0)