mirror of
https://github.com/mastodon/mastodon.git
synced 2025-05-11 20:21:10 +00:00
Merge fcbe8982e5
into fbe9728f36
This commit is contained in:
commit
727c171659
|
@ -23,6 +23,6 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseControl
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_translation
|
def set_translation
|
||||||
@translation = TranslateStatusService.new.call(@status, I18n.locale.to_s)
|
@translation = TranslateStatusService.new.call(@status, params[:source_language], I18n.locale.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -109,6 +109,7 @@ export function normalizeStatusTranslation(translation, status) {
|
||||||
const emojiMap = makeEmojiMap(status.get('emojis').toJS());
|
const emojiMap = makeEmojiMap(status.get('emojis').toJS());
|
||||||
|
|
||||||
const normalTranslation = {
|
const normalTranslation = {
|
||||||
|
source_language: translation.source_language,
|
||||||
detected_source_language: translation.detected_source_language,
|
detected_source_language: translation.detected_source_language,
|
||||||
language: translation.language,
|
language: translation.language,
|
||||||
provider: translation.provider,
|
provider: translation.provider,
|
||||||
|
|
|
@ -290,10 +290,10 @@ export function toggleStatusCollapse(id, isCollapsed) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const translateStatus = id => (dispatch) => {
|
export const translateStatus = (id, source_language) => (dispatch) => {
|
||||||
dispatch(translateStatusRequest(id));
|
dispatch(translateStatusRequest(id));
|
||||||
|
|
||||||
api().post(`/api/v1/statuses/${id}/translate`).then(response => {
|
api().post(`/api/v1/statuses/${id}/translate`, { source_language }).then(response => {
|
||||||
dispatch(translateStatusSuccess(id, response.data));
|
dispatch(translateStatusSuccess(id, response.data));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(translateStatusFail(id, error));
|
dispatch(translateStatusFail(id, error));
|
||||||
|
|
|
@ -108,6 +108,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onToggleHidden: PropTypes.func,
|
onToggleHidden: PropTypes.func,
|
||||||
onToggleCollapsed: PropTypes.func,
|
onToggleCollapsed: PropTypes.func,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
|
onUndoStatusTranslation: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
@ -193,8 +194,12 @@ class Status extends ImmutablePureComponent {
|
||||||
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
|
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleTranslate = () => {
|
handleTranslate = (sourceLanguage) => {
|
||||||
this.props.onTranslate(this._properStatus());
|
this.props.onTranslate(this._properStatus(), sourceLanguage);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleUndoStatusTranslation = () => {
|
||||||
|
this.props.onUndoStatusTranslation(this._properStatus());
|
||||||
};
|
};
|
||||||
|
|
||||||
getAttachmentAspectRatio () {
|
getAttachmentAspectRatio () {
|
||||||
|
@ -572,6 +577,7 @@ class Status extends ImmutablePureComponent {
|
||||||
status={status}
|
status={status}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onTranslate={this.handleTranslate}
|
onTranslate={this.handleTranslate}
|
||||||
|
onUndoStatusTranslation={this.handleUndoStatusTranslation}
|
||||||
collapsible
|
collapsible
|
||||||
onCollapsedToggle={this.handleCollapsedToggle}
|
onCollapsedToggle={this.handleCollapsedToggle}
|
||||||
{...statusContentProps}
|
{...statusContentProps}
|
||||||
|
|
|
@ -30,32 +30,47 @@ class TranslateButton extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
translation: ImmutablePropTypes.map,
|
translation: ImmutablePropTypes.map,
|
||||||
onClick: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
|
onDetectLanguage: PropTypes.func,
|
||||||
|
onUndoStatusTranslation: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { translation, onClick } = this.props;
|
const { translation, onTranslate, onDetectLanguage, onUndoStatusTranslation } = this.props;
|
||||||
|
|
||||||
if (translation) {
|
if (translation) {
|
||||||
const language = preloadedLanguages.find(lang => lang[0] === translation.get('detected_source_language'));
|
const language = preloadedLanguages.find(lang => lang[0] === translation.get('detected_source_language'));
|
||||||
const languageName = language ? language[1] : translation.get('detected_source_language');
|
const languageName = language ? language[1] : translation.get('detected_source_language');
|
||||||
const provider = translation.get('provider');
|
const provider = translation.get('provider');
|
||||||
|
|
||||||
|
const languageDetectionButton = onDetectLanguage && (
|
||||||
|
<>
|
||||||
|
<button className='link-button' onClick={onDetectLanguage}>
|
||||||
|
<FormattedMessage id='status.detect_language' defaultMessage='Detect language' />
|
||||||
|
</button>
|
||||||
|
·
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='translate-button'>
|
<div className='translate-button'>
|
||||||
<div className='translate-button__meta'>
|
<div className='translate-button__meta'>
|
||||||
<FormattedMessage id='status.translated_from_with' defaultMessage='Translated from {lang} using {provider}' values={{ lang: languageName, provider }} />
|
<FormattedMessage id='status.translated_from_with' defaultMessage='Translated from {lang} using {provider}' values={{ lang: languageName, provider }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className='link-button' onClick={onClick}>
|
<div className='translate-button__buttons'>
|
||||||
<FormattedMessage id='status.show_original' defaultMessage='Show original' />
|
{languageDetectionButton}
|
||||||
</button>
|
|
||||||
|
<button className='link-button' onClick={onUndoStatusTranslation}>
|
||||||
|
<FormattedMessage id='status.show_original' defaultMessage='Show original' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className='status__content__translate-button' onClick={onClick}>
|
<button className='status__content__translate-button' onClick={onTranslate}>
|
||||||
<FormattedMessage id='status.translate' defaultMessage='Translate' />
|
<FormattedMessage id='status.translate' defaultMessage='Translate' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -73,6 +88,7 @@ class StatusContent extends PureComponent {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
statusContent: PropTypes.string,
|
statusContent: PropTypes.string,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
|
onUndoStatusTranslation: PropTypes.func,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
collapsible: PropTypes.bool,
|
collapsible: PropTypes.bool,
|
||||||
onCollapsedToggle: PropTypes.func,
|
onCollapsedToggle: PropTypes.func,
|
||||||
|
@ -216,6 +232,10 @@ class StatusContent extends PureComponent {
|
||||||
this.props.onTranslate();
|
this.props.onTranslate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleDetectLanguage = () => {
|
||||||
|
this.props.onTranslate('und');
|
||||||
|
};
|
||||||
|
|
||||||
setRef = (c) => {
|
setRef = (c) => {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
};
|
};
|
||||||
|
@ -225,9 +245,13 @@ class StatusContent extends PureComponent {
|
||||||
|
|
||||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||||
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||||
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
const sourceLanguage = status.get('language') || 'und';
|
||||||
|
const targetLanguages = this.props.languages?.get(sourceLanguage);
|
||||||
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
|
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
|
||||||
|
|
||||||
|
const languageDetectionTargetLanguages = this.props.languages?.get('und');
|
||||||
|
const renderDetectLanguage = sourceLanguage !== 'und' && languageDetectionTargetLanguages?.includes(contentLocale) && status.get('translation')?.get('source_language') !== 'und';
|
||||||
|
|
||||||
const content = { __html: statusContent ?? getStatusContent(status) };
|
const content = { __html: statusContent ?? getStatusContent(status) };
|
||||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||||
const classNames = classnames('status__content', {
|
const classNames = classnames('status__content', {
|
||||||
|
@ -242,7 +266,12 @@ class StatusContent extends PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
const translateButton = renderTranslate && (
|
const translateButton = renderTranslate && (
|
||||||
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
<TranslateButton
|
||||||
|
onTranslate={this.handleTranslate}
|
||||||
|
onDetectLanguage={renderDetectLanguage && this.handleDetectLanguage}
|
||||||
|
onUndoStatusTranslation={this.props.onUndoStatusTranslation}
|
||||||
|
translation={status.get('translation')}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const poll = !!status.get('poll') && (
|
const poll = !!status.get('poll') && (
|
||||||
|
|
|
@ -122,12 +122,12 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onTranslate (status) {
|
onTranslate (status, sourceLanguage) {
|
||||||
if (status.get('translation')) {
|
dispatch(translateStatus(status.get('id'), sourceLanguage));
|
||||||
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
},
|
||||||
} else {
|
|
||||||
dispatch(translateStatus(status.get('id')));
|
onUndoStatusTranslation (status) {
|
||||||
}
|
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
||||||
},
|
},
|
||||||
|
|
||||||
onDirect (account) {
|
onDirect (account) {
|
||||||
|
|
|
@ -43,7 +43,8 @@ export const DetailedStatus: React.FC<{
|
||||||
status: any;
|
status: any;
|
||||||
onOpenMedia?: (status: any, index: number, lang: string) => void;
|
onOpenMedia?: (status: any, index: number, lang: string) => void;
|
||||||
onOpenVideo?: (status: any, lang: string, options: VideoModalOptions) => void;
|
onOpenVideo?: (status: any, lang: string, options: VideoModalOptions) => void;
|
||||||
onTranslate?: (status: any) => void;
|
onTranslate?: (status: any, sourceLanguage?: string) => void;
|
||||||
|
onUndoStatusTranslation?: (status: any) => void;
|
||||||
measureHeight?: boolean;
|
measureHeight?: boolean;
|
||||||
onHeightChange?: () => void;
|
onHeightChange?: () => void;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
@ -58,6 +59,7 @@ export const DetailedStatus: React.FC<{
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onOpenVideo,
|
onOpenVideo,
|
||||||
onTranslate,
|
onTranslate,
|
||||||
|
onUndoStatusTranslation,
|
||||||
measureHeight,
|
measureHeight,
|
||||||
onHeightChange,
|
onHeightChange,
|
||||||
domain,
|
domain,
|
||||||
|
@ -115,9 +117,16 @@ export const DetailedStatus: React.FC<{
|
||||||
[_measureHeight],
|
[_measureHeight],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTranslate = useCallback(() => {
|
const handleTranslate = useCallback(
|
||||||
if (onTranslate) onTranslate(status);
|
(sourceLanguage?: string) => {
|
||||||
}, [onTranslate, status]);
|
if (onTranslate) onTranslate(status, sourceLanguage);
|
||||||
|
},
|
||||||
|
[onTranslate, status],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUndoStatusTranslation = useCallback(() => {
|
||||||
|
if (onUndoStatusTranslation) onUndoStatusTranslation(status);
|
||||||
|
}, [onUndoStatusTranslation, status]);
|
||||||
|
|
||||||
if (!properStatus) {
|
if (!properStatus) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -368,6 +377,7 @@ export const DetailedStatus: React.FC<{
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
onTranslate={handleTranslate}
|
onTranslate={handleTranslate}
|
||||||
|
onUndoStatusTranslation={handleUndoStatusTranslation}
|
||||||
{...(statusContentProps as any)}
|
{...(statusContentProps as any)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -339,14 +339,16 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleTranslate = status => {
|
handleTranslate = (status, sourceLanguage) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
if (status.get('translation')) {
|
dispatch(translateStatus(status.get('id'), sourceLanguage));
|
||||||
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
};
|
||||||
} else {
|
|
||||||
dispatch(translateStatus(status.get('id')));
|
handleUndoStatusTranslation = status => {
|
||||||
}
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
|
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBlockClick = (status) => {
|
handleBlockClick = (status) => {
|
||||||
|
@ -627,6 +629,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
onToggleHidden={this.handleToggleHidden}
|
onToggleHidden={this.handleToggleHidden}
|
||||||
onTranslate={this.handleTranslate}
|
onTranslate={this.handleTranslate}
|
||||||
|
onUndoStatusTranslation={this.handleUndoStatusTranslation}
|
||||||
domain={domain}
|
domain={domain}
|
||||||
showMedia={this.state.showMedia}
|
showMedia={this.state.showMedia}
|
||||||
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
||||||
|
|
|
@ -840,6 +840,7 @@
|
||||||
"status.copy": "Copy link to post",
|
"status.copy": "Copy link to post",
|
||||||
"status.delete": "Delete",
|
"status.delete": "Delete",
|
||||||
"status.detailed_status": "Detailed conversation view",
|
"status.detailed_status": "Detailed conversation view",
|
||||||
|
"status.detect_language": "Detect language",
|
||||||
"status.direct": "Privately mention @{name}",
|
"status.direct": "Privately mention @{name}",
|
||||||
"status.direct_indicator": "Private mention",
|
"status.direct_indicator": "Private mention",
|
||||||
"status.edit": "Edit",
|
"status.edit": "Edit",
|
||||||
|
|
|
@ -1383,6 +1383,17 @@ body > [data-popper-placement] {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
|
|
||||||
|
.translate-button__buttons {
|
||||||
|
margin-right: -6px;
|
||||||
|
float: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__wrapper--filtered {
|
.status__wrapper--filtered {
|
||||||
|
|
|
@ -11,6 +11,7 @@ class TranslationService::DeepL < TranslationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate(texts, source_language, target_language)
|
def translate(texts, source_language, target_language)
|
||||||
|
source_language = nil if source_language == 'und'
|
||||||
form = { text: texts, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' }
|
form = { text: texts, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' }
|
||||||
request(:post, '/v2/translate', form: form) do |res|
|
request(:post, '/v2/translate', form: form) do |res|
|
||||||
transform_response(res.body_with_limit)
|
transform_response(res.body_with_limit)
|
||||||
|
@ -18,7 +19,7 @@ class TranslationService::DeepL < TranslationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def languages
|
def languages
|
||||||
source_languages = [nil] + fetch_languages('source')
|
source_languages = ['und'] + fetch_languages('source')
|
||||||
|
|
||||||
# In DeepL, EN and PT are deprecated in favor of EN-GB/EN-US and PT-BR/PT-PT, so
|
# In DeepL, EN and PT are deprecated in favor of EN-GB/EN-US and PT-BR/PT-PT, so
|
||||||
# they are supported but not returned by the API.
|
# they are supported but not returned by the API.
|
||||||
|
|
|
@ -9,7 +9,8 @@ class TranslationService::LibreTranslate < TranslationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate(texts, source_language, target_language)
|
def translate(texts, source_language, target_language)
|
||||||
body = Oj.dump(q: texts, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key)
|
source_language = 'auto' if source_language.in? [nil, 'und']
|
||||||
|
body = Oj.dump(q: texts, source: source_language, target: target_language, format: 'html', api_key: @api_key)
|
||||||
request(:post, '/translate', body: body) do |res|
|
request(:post, '/translate', body: body) do |res|
|
||||||
transform_response(res.body_with_limit, source_language)
|
transform_response(res.body_with_limit, source_language)
|
||||||
end
|
end
|
||||||
|
@ -20,7 +21,7 @@ class TranslationService::LibreTranslate < TranslationService
|
||||||
languages = Oj.load(res.body_with_limit).to_h do |language|
|
languages = Oj.load(res.body_with_limit).to_h do |language|
|
||||||
[language['code'], language['targets'].without(language['code'])]
|
[language['code'], language['targets'].without(language['code'])]
|
||||||
end
|
end
|
||||||
languages[nil] = languages.values.flatten.uniq.sort
|
languages['und'] = languages.values.flatten.uniq.sort
|
||||||
languages
|
languages
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Translation < ActiveModelSerializers::Model
|
class Translation < ActiveModelSerializers::Model
|
||||||
attributes :status, :detected_source_language, :language, :provider,
|
attributes :status, :source_language, :detected_source_language, :language, :provider,
|
||||||
:content, :spoiler_text, :poll_options, :media_attachments
|
:content, :spoiler_text, :poll_options, :media_attachments
|
||||||
|
|
||||||
class Option < ActiveModelSerializers::Model
|
class Option < ActiveModelSerializers::Model
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::TranslationSerializer < ActiveModel::Serializer
|
class REST::TranslationSerializer < ActiveModel::Serializer
|
||||||
attributes :detected_source_language, :language, :provider, :spoiler_text, :content
|
attributes :source_language, :detected_source_language, :language, :provider, :spoiler_text, :content
|
||||||
|
|
||||||
class PollSerializer < ActiveModel::Serializer
|
class PollSerializer < ActiveModel::Serializer
|
||||||
attribute :id
|
attribute :id
|
||||||
|
|
|
@ -6,17 +6,18 @@ class TranslateStatusService < BaseService
|
||||||
include ERB::Util
|
include ERB::Util
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
def call(status, target_language)
|
def call(status, source_language, target_language)
|
||||||
@status = status
|
@status = status
|
||||||
@source_texts = source_texts
|
@source_texts = source_texts
|
||||||
|
@source_language = source_language || @status.language.presence || 'und'
|
||||||
|
|
||||||
target_language = target_language.split(/[_-]/).first unless target_languages.include?(target_language)
|
target_language = target_language.split(/[_-]/).first unless target_languages.include?(target_language)
|
||||||
@target_language = target_language
|
@target_language = target_language
|
||||||
|
|
||||||
raise Mastodon::NotPermittedError unless permitted?
|
raise Mastodon::NotPermittedError unless permitted?
|
||||||
|
|
||||||
status_translation = Rails.cache.fetch("v2:translations/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do
|
status_translation = Rails.cache.fetch("v3:translations/#{@source_language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) do
|
||||||
translations = translation_backend.translate(@source_texts.values, @status.language, @target_language)
|
translations = translation_backend.translate(@source_texts.values, @source_language, @target_language)
|
||||||
build_status_translation(translations)
|
build_status_translation(translations)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ class TranslateStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def languages
|
def languages
|
||||||
Rails.cache.fetch('translation_service/languages', expires_in: 7.days, race_condition_ttl: 1.hour) { translation_backend.languages }
|
Rails.cache.fetch('v2:translation_service/languages', expires_in: 7.days, race_condition_ttl: 1.hour) { translation_backend.languages }
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_languages
|
def target_languages
|
||||||
|
@ -67,6 +68,7 @@ class TranslateStatusService < BaseService
|
||||||
|
|
||||||
def build_status_translation(translations)
|
def build_status_translation(translations)
|
||||||
status_translation = Translation.new(
|
status_translation = Translation.new(
|
||||||
|
source_language: @source_language,
|
||||||
detected_source_language: translations.first&.detected_source_language,
|
detected_source_language: translations.first&.detected_source_language,
|
||||||
language: @target_language,
|
language: @target_language,
|
||||||
provider: translations.first&.provider,
|
provider: translations.first&.provider,
|
||||||
|
|
|
@ -38,7 +38,7 @@ RSpec.describe TranslationService::DeepL do
|
||||||
.with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html')
|
.with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html')
|
||||||
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"}]}')
|
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"}]}')
|
||||||
|
|
||||||
translations = service.translate(['Guten Tag'], nil, 'en')
|
translations = service.translate(['Guten Tag'], 'und', 'en')
|
||||||
expect(translations.size).to eq 1
|
expect(translations.size).to eq 1
|
||||||
|
|
||||||
translation = translations.first
|
translation = translations.first
|
||||||
|
@ -62,7 +62,7 @@ RSpec.describe TranslationService::DeepL do
|
||||||
|
|
||||||
describe '#languages' do
|
describe '#languages' do
|
||||||
it 'returns source languages' do
|
it 'returns source languages' do
|
||||||
expect(service.languages.keys).to eq [nil, 'en', 'uk']
|
expect(service.languages.keys).to eq %w(und en uk)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns target languages for each source language' do
|
it 'returns target languages for each source language' do
|
||||||
|
@ -71,7 +71,7 @@ RSpec.describe TranslationService::DeepL do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns target languages for auto-detection' do
|
it 'returns target languages for auto-detection' do
|
||||||
expect(service.languages[nil]).to eq %w(en pt en-GB zh)
|
expect(service.languages['und']).to eq %w(en pt en-GB zh)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ RSpec.describe TranslationService::LibreTranslate do
|
||||||
subject(:languages) { service.languages }
|
subject(:languages) { service.languages }
|
||||||
|
|
||||||
it 'returns source languages' do
|
it 'returns source languages' do
|
||||||
expect(languages.keys).to eq ['en', 'da', nil]
|
expect(languages.keys).to eq %w(en da und)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns target languages for each source language' do
|
it 'returns target languages for each source language' do
|
||||||
|
@ -24,7 +24,7 @@ RSpec.describe TranslationService::LibreTranslate do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns target languages for auto-detected language' do
|
it 'returns target languages for auto-detected language' do
|
||||||
expect(languages[nil]).to eq %w(de en es pt)
|
expect(languages['und']).to eq %w(de en es pt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ RSpec.describe 'API V1 Statuses Translations' do
|
||||||
translation = TranslationService::Translation.new(text: 'Hello')
|
translation = TranslationService::Translation.new(text: 'Hello')
|
||||||
service = instance_double(TranslationService::DeepL, translate: [translation])
|
service = instance_double(TranslationService::DeepL, translate: [translation])
|
||||||
allow(TranslationService).to receive_messages(configured?: true, configured: service)
|
allow(TranslationService).to receive_messages(configured?: true, configured: service)
|
||||||
Rails.cache.write('translation_service/languages', { 'es' => ['en'] })
|
Rails.cache.write('v2:translation_service/languages', { 'es' => ['en'] })
|
||||||
post "/api/v1/statuses/#{status.id}/translate", headers: headers
|
post "/api/v1/statuses/#{status.id}/translate", headers: headers
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe TranslateStatusService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns translated status content and source language and provider and original status' do
|
it 'returns translated status content and source language and provider and original status' do
|
||||||
expect(service.call(status, 'es'))
|
expect(service.call(status, nil, 'es'))
|
||||||
.to have_attributes(
|
.to have_attributes(
|
||||||
content: '<p>Hola</p>',
|
content: '<p>Hola</p>',
|
||||||
detected_source_language: 'en',
|
detected_source_language: 'en',
|
||||||
|
@ -47,13 +47,13 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:text) { 'Hello & :highfive:' }
|
let(:text) { 'Hello & :highfive:' }
|
||||||
|
|
||||||
it 'does not translate shortcode' do
|
it 'does not translate shortcode' do
|
||||||
expect(service.call(status, 'es').content).to eq '<p>Hola & :highfive:</p>'
|
expect(service.call(status, nil, 'es').content).to eq '<p>Hola & :highfive:</p>'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'status has no spoiler_text' do
|
describe 'status has no spoiler_text' do
|
||||||
it 'returns an empty string' do
|
it 'returns an empty string' do
|
||||||
expect(service.call(status, 'es').spoiler_text).to eq ''
|
expect(service.call(status, nil, 'es').spoiler_text).to eq ''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:spoiler_text) { 'Hello & Hello!' }
|
let(:spoiler_text) { 'Hello & Hello!' }
|
||||||
|
|
||||||
it 'translates the spoiler text' do
|
it 'translates the spoiler text' do
|
||||||
expect(service.call(status, 'es').spoiler_text).to eq 'Hola & Hola!'
|
expect(service.call(status, nil, 'es').spoiler_text).to eq 'Hola & Hola!'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:spoiler_text) { 'Hello :highfive:' }
|
let(:spoiler_text) { 'Hello :highfive:' }
|
||||||
|
|
||||||
it 'does not translate shortcode' do
|
it 'does not translate shortcode' do
|
||||||
expect(service.call(status, 'es').spoiler_text).to eq 'Hola :highfive:'
|
expect(service.call(status, nil, 'es').spoiler_text).to eq 'Hola :highfive:'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:spoiler_text) { 'Hello :Hello:' }
|
let(:spoiler_text) { 'Hello :Hello:' }
|
||||||
|
|
||||||
it 'translates the invalid shortcode' do
|
it 'translates the invalid shortcode' do
|
||||||
expect(service.call(status, 'es').spoiler_text).to eq 'Hola :Hola:'
|
expect(service.call(status, nil, 'es').spoiler_text).to eq 'Hola :Hola:'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:poll) { Fabricate(:poll, options: ['Hello 1', 'Hello 2']) }
|
let(:poll) { Fabricate(:poll, options: ['Hello 1', 'Hello 2']) }
|
||||||
|
|
||||||
it 'translates the poll option title' do
|
it 'translates the poll option title' do
|
||||||
status_translation = service.call(status, 'es')
|
status_translation = service.call(status, nil, 'es')
|
||||||
expect(status_translation.poll_options.size).to eq 2
|
expect(status_translation.poll_options.size).to eq 2
|
||||||
expect(status_translation.poll_options.first.title).to eq 'Hola 1'
|
expect(status_translation.poll_options.first.title).to eq 'Hola 1'
|
||||||
end
|
end
|
||||||
|
@ -95,7 +95,7 @@ RSpec.describe TranslateStatusService do
|
||||||
let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello & :highfive:')] }
|
let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello & :highfive:')] }
|
||||||
|
|
||||||
it 'translates the media attachment description' do
|
it 'translates the media attachment description' do
|
||||||
status_translation = service.call(status, 'es')
|
status_translation = service.call(status, nil, 'es')
|
||||||
|
|
||||||
media_attachment = status_translation.media_attachments.first
|
media_attachment = status_translation.media_attachments.first
|
||||||
expect(media_attachment.id).to eq media_attachments.first.id
|
expect(media_attachment.id).to eq media_attachments.first.id
|
||||||
|
@ -105,11 +105,11 @@ RSpec.describe TranslateStatusService do
|
||||||
|
|
||||||
describe 'target language is regional' do
|
describe 'target language is regional' do
|
||||||
it 'uses regional variant' do
|
it 'uses regional variant' do
|
||||||
expect(service.call(status, 'es-MX').language).to eq 'es-MX'
|
expect(service.call(status, nil, 'es-MX').language).to eq 'es-MX'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses parent locale for unsupported regional variant' do
|
it 'uses parent locale for unsupported regional variant' do
|
||||||
expect(service.call(status, 'es-XX').language).to eq 'es'
|
expect(service.call(status, nil, 'es-XX').language).to eq 'es'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user