feat: add support for the Translation API (#3314)
This commit is contained in:
parent
73f6790d01
commit
d22579fd89
3 changed files with 76 additions and 15 deletions
|
@ -11,7 +11,7 @@ const {
|
||||||
withAction?: boolean
|
withAction?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { translation } = useTranslation(status, getLanguageCode())
|
const { translation } = await useTranslation(status, getLanguageCode())
|
||||||
|
|
||||||
const emojisObject = useEmojisFallback(() => status.emojis)
|
const emojisObject = useEmojisFallback(() => status.emojis)
|
||||||
const vnode = computed(() => {
|
const vnode = computed(() => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ const {
|
||||||
toggle: _toggleTranslation,
|
toggle: _toggleTranslation,
|
||||||
translation,
|
translation,
|
||||||
enabled: isTranslationEnabled,
|
enabled: isTranslationEnabled,
|
||||||
} = useTranslation(status, getLanguageCode())
|
} = await useTranslation(status, getLanguageCode())
|
||||||
const preferenceHideTranslation = usePreferences('hideTranslation')
|
const preferenceHideTranslation = usePreferences('hideTranslation')
|
||||||
|
|
||||||
const showButton = computed(() =>
|
const showButton = computed(() =>
|
||||||
|
|
|
@ -42,6 +42,10 @@ export const supportedTranslationCodes = [
|
||||||
'zh',
|
'zh',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
const translationAPISupported = 'Translator' in globalThis && 'LanguageDetector' in globalThis
|
||||||
|
|
||||||
|
const anchorMarkupRegEx = /<a[^>]*>.*?<\/a>/g
|
||||||
|
|
||||||
export function getLanguageCode() {
|
export function getLanguageCode() {
|
||||||
let code = 'en'
|
let code = 'en'
|
||||||
const getCode = (code: string) => code.replace(/-.*$/, '')
|
const getCode = (code: string) => code.replace(/-.*$/, '')
|
||||||
|
@ -58,6 +62,13 @@ interface TranslationErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function replaceTranslatedLinksWithOriginal(text: string) {
|
||||||
|
return text.replace(anchorMarkupRegEx, (match) => {
|
||||||
|
const tagLink = anchorMarkupRegEx.exec(text)
|
||||||
|
return tagLink ? tagLink[0] : match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function translateText(text: string, from: string | null | undefined, to: string) {
|
export async function translateText(text: string, from: string | null | undefined, to: string) {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const status = ref({
|
const status = ref({
|
||||||
|
@ -65,7 +76,6 @@ export async function translateText(text: string, from: string | null | undefine
|
||||||
error: '',
|
error: '',
|
||||||
text: '',
|
text: '',
|
||||||
})
|
})
|
||||||
const regex = /<a[^>]*>.*?<\/a>/g
|
|
||||||
try {
|
try {
|
||||||
const response = await ($fetch as any)(config.public.translateApi, {
|
const response = await ($fetch as any)(config.public.translateApi, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -78,11 +88,7 @@ export async function translateText(text: string, from: string | null | undefine
|
||||||
},
|
},
|
||||||
}) as TranslationResponse
|
}) as TranslationResponse
|
||||||
status.value.success = true
|
status.value.success = true
|
||||||
// replace the translated links with the original
|
status.value.text = replaceTranslatedLinksWithOriginal(response.translatedText)
|
||||||
status.value.text = response.translatedText.replace(regex, (match) => {
|
|
||||||
const tagLink = regex.exec(text)
|
|
||||||
return tagLink ? tagLink[0] : match
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
// TODO: improve type
|
// TODO: improve type
|
||||||
|
@ -102,17 +108,27 @@ const translations = new WeakMap<mastodon.v1.Status | mastodon.v1.StatusEdit, {
|
||||||
error: string
|
error: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEdit, to: string) {
|
export async function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEdit, to: string) {
|
||||||
if (!translations.has(status))
|
if (!translations.has(status))
|
||||||
translations.set(status, reactive({ visible: false, text: '', success: false, error: '' }))
|
translations.set(status, reactive({ visible: false, text: '', success: false, error: '' }))
|
||||||
|
|
||||||
const translation = translations.get(status)!
|
const translation = translations.get(status)!
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
|
|
||||||
const shouldTranslate = 'language' in status && status.language && status.language !== to
|
let shouldTranslate = false
|
||||||
&& supportedTranslationCodes.includes(to as any)
|
if ('language' in status) {
|
||||||
|
shouldTranslate = typeof status.language === 'string' && status.language !== to && !userSettings.value.disabledTranslationLanguages.includes(status.language)
|
||||||
|
if (!translationAPISupported) {
|
||||||
|
shouldTranslate = shouldTranslate && supportedTranslationCodes.includes(to as any)
|
||||||
&& supportedTranslationCodes.includes(status.language as any)
|
&& supportedTranslationCodes.includes(status.language as any)
|
||||||
&& !userSettings.value.disabledTranslationLanguages.includes(status.language)
|
}
|
||||||
|
else {
|
||||||
|
shouldTranslate = shouldTranslate && (await (globalThis as any).Translator.availability({
|
||||||
|
sourceLanguage: status.language,
|
||||||
|
targetLanguage: to,
|
||||||
|
})) !== 'unavailable'
|
||||||
|
}
|
||||||
|
}
|
||||||
const enabled = /*! !useRuntimeConfig().public.translateApi && */ shouldTranslate
|
const enabled = /*! !useRuntimeConfig().public.translateApi && */ shouldTranslate
|
||||||
|
|
||||||
async function toggle() {
|
async function toggle() {
|
||||||
|
@ -120,12 +136,57 @@ export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEd
|
||||||
return
|
return
|
||||||
|
|
||||||
if (!translation.text) {
|
if (!translation.text) {
|
||||||
const translated = await translateText(status.content, status.language, to)
|
let translated = {
|
||||||
|
value: {
|
||||||
|
error: '',
|
||||||
|
text: '',
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (translationAPISupported && 'language' in status) {
|
||||||
|
let sourceLanguage = status.language
|
||||||
|
if (!sourceLanguage) {
|
||||||
|
const languageDetector = await (globalThis as any).LanguageDetector.create()
|
||||||
|
// Make sure HTML markup doesn't derail language detection.
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerHTML = status.content
|
||||||
|
// eslint-disable-next-line unicorn/prefer-dom-node-text-content
|
||||||
|
const detectedLanguages = await languageDetector.detect(div.innerText)
|
||||||
|
sourceLanguage = detectedLanguages[0].detectedLanguage
|
||||||
|
if (sourceLanguage === 'und') {
|
||||||
|
throw new Error('Could not detect source language.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const translator = await (globalThis as any).Translator.create({
|
||||||
|
sourceLanguage,
|
||||||
|
targetLanguage: to,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
let text = await translator.translate(status.content)
|
||||||
|
text = replaceTranslatedLinksWithOriginal(text)
|
||||||
|
translated.value = {
|
||||||
|
error: '',
|
||||||
|
text,
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
translated.value = {
|
||||||
|
error: (error as Error).message,
|
||||||
|
text: '',
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ('language' in status) {
|
||||||
|
translated = await translateText(status.content, status.language, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
translation.error = translated.value.error
|
translation.error = translated.value.error
|
||||||
translation.text = translated.value.text
|
translation.text = translated.value.text
|
||||||
translation.success = translated.value.success
|
translation.success = translated.value.success
|
||||||
}
|
}
|
||||||
|
|
||||||
translation.visible = !translation.visible
|
translation.visible = !translation.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue