This commit is contained in:
pengxiaolong
2025-05-13 19:39:53 +08:00
parent 37da6765b8
commit c006a8e63d
1232 changed files with 96963 additions and 883 deletions

View File

@@ -0,0 +1,124 @@
<template>
<div
v-if="translationVisible"
ref="translationWrapperRef"
:class="{
'message-translation': true,
'reverse': props.message.flow === 'out',
'error': hasTranslationError,
}"
>
<TranslationContent
:message="props.message"
:translationContentVisible="translationVisible"
:translationWrapperRef="translationWrapperRef"
:isSingleTranslation="isSingleTranslation"
@toggleErrorStatus="toggleErrorStatus"
/>
<div class="copyright">
<Icon
:file="checkIcon"
size="13px"
/>
<div class="copyright-text">
{{ TUITranslateService.t('TUIChat.由IM提供翻译支持') }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from '../../../../../adapter-vue';
import {
TUIStore,
StoreName,
IMessageModel,
TUITranslateService,
} from '@tencentcloud/chat-uikit-engine';
import Icon from '../../../../common/Icon.vue';
import TranslationContent from './translation-content.vue';
import checkIcon from '../../../../../assets/icon/check-sm.svg';
import { ITranslateInfo } from '../../../../../interface';
interface IProps {
message: IMessageModel;
}
const props = withDefaults(defineProps<IProps>(), {
message: () => ({} as IMessageModel),
});
const translationVisible = ref<boolean>(false);
const hasTranslationError = ref<boolean>(false);
const translationWrapperRef = ref<HTMLDivElement>();
let isSingleTranslation = true;
onMounted(() => {
TUIStore.watch(StoreName.CHAT, {
translateTextInfo: onMessageTranslationUpdated,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CHAT, {
translateTextInfo: onMessageTranslationUpdated,
});
});
function toggleErrorStatus(hasError: boolean) {
hasTranslationError.value = hasError;
}
function onMessageTranslationUpdated(info: Map<string, ITranslateInfo[]>) {
if (info === undefined) return;
isSingleTranslation = false;
const translationInfoList = info.get(props.message.conversationID) || [];
for (let i = 0; i < translationInfoList.length; ++i) {
const { messageID, visible } = translationInfoList[i];
if (messageID === props.message.ID && visible !== undefined) {
if (translationInfoList.length === 1 && visible) {
isSingleTranslation = true;
}
hasTranslationError.value = false;
translationVisible.value = visible;
break;
}
}
}
</script>
<style lang="scss" scoped>
.message-translation {
margin-top: 4px;
margin-left: 44px;
padding: 10px;
background-color: #f2f7ff;
border-radius: 10px;
display: flex;
flex-direction: column !important;
transition: background-color 0.15s ease-out;
&.error {
background-color: #ffdfdf;
}
.copyright {
display: flex;
align-items: center;
margin-top: 10px;
.copyright-text {
margin-left: 2px;
font-size: 12px;
color: #999;
}
}
}
.message-translation.reverse {
margin-right: 44px;
margin-left: auto;
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<div class="message-translation-container">
<div
v-if="translationFinished"
:id="`translation-content-${props.message.ID}`"
:class="{
'translation-content': true,
'occur': true
}"
>
<template
v-if="translationTextList.length > 0"
>
<span
v-for="(text, index) in translationTextList"
:key="index"
>
<img
v-if="text.type === 'face'"
class="text-face"
:src="text.value"
>
<span
v-else
class="text-plain"
>
{{ text.value }}
</span>
</span>
</template>
<template v-else>
{{ translationErrorText }}
</template>
</div>
<div
:class="{
'loading': true,
'loading-end': translationFinished
}"
>
{{ TUITranslateService.t('TUIChat.翻译中') }}...
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from '../../../../../adapter-vue';
import {
IMessageModel,
TUITranslateService,
} from '@tencentcloud/chat-uikit-engine';
import { TranslationTextType, translator } from '../../../utils/translation';
interface IProps {
message: IMessageModel;
translationContentVisible: boolean;
isSingleTranslation: boolean;
translationWrapperRef: HTMLDivElement | undefined;
}
const props = withDefaults(defineProps<IProps>(), {
message: () => ({} as IMessageModel),
});
const translationFinished = ref<boolean>(false);
const translationErrorText = ref<string>('');
const translationTextList = ref<TranslationTextType[]>([]);
watch(() => props.translationContentVisible, (newVal: boolean) => {
if (newVal) {
translator.get(props.message)
.then((result) => {
translationFinished.value = true;
translationTextList.value = result;
})
.catch((err) => {
translationFinished.value = true;
emits('toggleErrorStatus', true);
translationErrorText.value = err.message;
});
}
}, { immediate: true });
</script>
<style lang="scss" scoped>
.message-translation-container {
min-height: 16px;
min-width: 80px;
position: relative;
transition: width 0.15s ease-out, height 0.15s ease-out, ;
font-size: 14px;
.loading {
position: absolute;
top: 0;
left: 0;
opacity: 1;
transition: opacity 0.3s ease-out;
&.loading-end {
opacity: 0;
}
}
.translation-content {
opacity: 0;
&.occur {
animation: occur 0.3s ease-out 0.45s forwards;
@keyframes occur {
100% {
opacity: 1;
}
}
}
.text-face {
width: 20px;
height: 20px;
}
}
}
</style>