Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
8 changes: 7 additions & 1 deletion Explorer/Assets/DCL/Chat/ChatBubbleComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ namespace DCL.Chat
{
public struct ChatBubbleComponent
{
public readonly string ChatMessage;
public readonly string MessageId;
public string ChatMessage;
public readonly bool IsMention;
public readonly string SenderDisplayName;
public readonly string RecipientValidatedName;
Expand All @@ -16,8 +17,11 @@ public struct ChatBubbleComponent
public readonly string CommunityName;
public readonly bool IsOwnMessage;
public bool IsDirty;

public bool IsTranslationUpdate;
public readonly Color RecipientNameColor;
public ChatBubbleComponent(
string messageId,
string chatMessage,
string senderDisplayName,
string senderWalletId,
Expand All @@ -31,6 +35,7 @@ public ChatBubbleComponent(
bool isCommunityMessage,
string communityName)
{
MessageId = messageId;
ChatMessage = chatMessage;
SenderDisplayName = senderDisplayName;
SenderWalletId = senderWalletId;
Expand All @@ -42,6 +47,7 @@ public ChatBubbleComponent(
RecipientWalletId = recipientWalletId;
RecipientNameColor = recipientNameColor;
IsDirty = true;
IsTranslationUpdate = false;
IsCommunityMessage = isCommunityMessage;
CommunityName = communityName;
}
Expand Down
18 changes: 18 additions & 0 deletions Explorer/Assets/DCL/Chat/History/ChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,23 @@ public static class ChatUtils
/// </summary>
public static string GetId(string walletId, double timestampRaw) =>
$"{walletId}:{timestampRaw.ToString(CultureInfo.InvariantCulture)}";

/// <summary>
/// Recovers the sender wallet address from an id produced by <see cref="GetId" />.
/// Returns false for ids that don't follow the composite format (e.g. system-message GUIDs).
/// </summary>
public static bool TryGetSenderWalletAddress(string messageId, out string walletAddress)
{
int separator = messageId.IndexOf(':');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Potential NullReferenceException

messageId.IndexOf(':') throws if messageId is null. TranslationEvents.MessageTranslated.MessageId is a struct field — its default is null — so any caller that forgets to set it will crash here.

All current callers always set it, so this is low risk today, but since the method is public static a null guard is cheap insurance:

Suggested change
int separator = messageId.IndexOf(':');
public static bool TryGetSenderWalletAddress(string messageId, out string walletAddress)
{
if (string.IsNullOrEmpty(messageId))
{
walletAddress = string.Empty;
return false;
}
int separator = messageId.IndexOf(':');


if (separator <= 0)
{
walletAddress = string.Empty;
return false;
}

walletAddress = messageId.Substring(0, separator);
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using DCL.Nametags;
using DCL.Profiles;
using DCL.Settings.Settings;
using DCL.Translation;
using DCL.Utilities;
using System;
using UnityEngine;
using UnityEngine.InputSystem;
using Utility;
using Utility.Arch;

namespace DCL.Chat.ChatServices
Expand All @@ -23,8 +25,12 @@ public class ChatWorldBubbleService : IDisposable
private readonly ChatSettingsAsset chatSettings;
private readonly IChatHistory chatHistory;
private readonly ICommunityDataService communityDataService;
private readonly IEventBus chatEventBus;
private readonly ITranslationSettings translationSettings;
private static readonly Color DEFAULT_COLOR = Color.white;

private readonly IDisposable translationSubscription;

public ChatWorldBubbleService(
World world,
Entity playerEntity,
Expand All @@ -33,7 +39,9 @@ public ChatWorldBubbleService(
NametagsData nametagsData,
ChatSettingsAsset chatSettings,
IChatHistory chatHistory,
ICommunityDataService communityDataService)
ICommunityDataService communityDataService,
IEventBus chatEventBus,
ITranslationSettings translationSettings)
{
this.world = world;
this.playerEntity = playerEntity;
Expand All @@ -43,8 +51,11 @@ public ChatWorldBubbleService(
this.chatSettings = chatSettings;
this.chatHistory = chatHistory;
this.communityDataService = communityDataService;
this.chatEventBus = chatEventBus;
this.translationSettings = translationSettings;

chatHistory.MessageAdded += OnChatMessageAdded;
translationSubscription = chatEventBus.Subscribe<TranslationEvents.MessageTranslated>(OnMessageTranslated);
DCLInput.Instance.Shortcuts.ToggleNametags.performed += OnToggleNametagsShortcutPerformed;
}

Expand Down Expand Up @@ -120,6 +131,7 @@ private void GenerateChatBubbleComponent(Entity e, ChatMessage chatMessage, Colo
if (!world.Has<NametagHolder>(e)) return;

world.AddOrSet(e, new ChatBubbleComponent(
chatMessage.MessageId,
chatMessage.Message,
chatMessage.SenderValidatedName,
chatMessage.SenderWalletAddress,
Expand All @@ -134,10 +146,40 @@ private void GenerateChatBubbleComponent(Entity e, ChatMessage chatMessage, Colo
communityName ?? string.Empty));
}

private void OnMessageTranslated(TranslationEvents.MessageTranslated evt)
{
MessageTranslation translation = evt.Translation;

if (translation.State != TranslationState.Success || string.IsNullOrEmpty(translation.TranslatedBody))
return;

// The message id encodes the sender wallet address, which the participant table maps to the live avatar entity.
if (!ChatUtils.TryGetSenderWalletAddress(evt.MessageId, out string senderWalletAddress)
|| !entityParticipantTable.TryGet(senderWalletAddress, out var entry)
|| !world.IsAlive(entry.Entity))
return;

ref ChatBubbleComponent bubble = ref world.TryGetRef<ChatBubbleComponent>(entry.Entity, out bool exists);

// Guard against the sender having since posted a newer message into the same bubble.
if (!exists || bubble.MessageId != evt.MessageId)
return;

// Mirror automatic translations only. A manual translate from the chat panel targets a conversation
// where auto-translate is off (or an older message whose bubble is already gone), so it is ignored here.
if (!translationSettings.GetAutoTranslateForConversation(bubble.ChannelId))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: global feature flag not checked

The per-conversation check is here but the global IsTranslationFeatureActive() guard is missing. If a translation is still in-flight when the user disables the global flag, OnMessageTranslated will still fire (the async work was already kicked off) and the bubble text will be swapped — contradicting the documented behaviour ("global translation feature flag … respected").

ChatMessageFeedPresenter correctly gates on both:

// from ChatMessageFeedPresenter, line ~119 / 354
if (!translationSettings.IsTranslationFeatureActive())if (!translationSettings.GetAutoTranslateForConversation())

Fix:

Suggested change
if (!translationSettings.GetAutoTranslateForConversation(bubble.ChannelId))
// Guard against in-flight translations arriving after the global feature is disabled.
if (!translationSettings.IsTranslationFeatureActive())
return;
// Mirror automatic translations only. A manual translate from the chat panel targets a conversation
// where auto-translate is off (or an older message whose bubble is already gone), so it is ignored here.
if (!translationSettings.GetAutoTranslateForConversation(bubble.ChannelId))
return;

return;

bubble.ChatMessage = translation.TranslatedBody;
bubble.IsTranslationUpdate = true;
bubble.IsDirty = true;
}

public void Dispose()
{
DCLInput.Instance.Shortcuts.ToggleNametags.performed -= OnToggleNametagsShortcutPerformed;
chatHistory.MessageAdded -= OnChatMessageAdded;
translationSubscription.Dispose();
}

private void OnToggleNametagsShortcutPerformed(InputAction.CallbackContext obj)
Expand Down
1 change: 1 addition & 0 deletions Explorer/Assets/DCL/NameTags/Assets/NametagStyle.uss
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@
font-size: 16px;
white-space: normal;
margin-top: 10px;
-unity-font-definition: url("project://database/Assets/DCL/UIToolkit/Fonts/Inter_18pt-SemiBold.ttf?fileID=12800000&guid=3d5604d303a734a85bf5000c0e2e5d92&type=3#Inter_18pt-SemiBold");
}

.safe-easing {
Expand Down
15 changes: 13 additions & 2 deletions Explorer/Assets/DCL/NameTags/Systems/NametagPlacementSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,20 @@ private void ProcessChatBubbleComponents(in NametagHolder nametagHolder, ref Cha
if (!chatBubbleComponent.IsDirty)
return;

nametagHolder.Nametag.DisplayMessage(chatBubbleComponent.ChatMessage, chatBubbleComponent.IsMention, chatBubbleComponent.IsPrivateMessage, chatBubbleComponent.IsOwnMessage, chatBubbleComponent.RecipientValidatedName, chatBubbleComponent.RecipientWalletId, chatBubbleComponent.RecipientNameColor, chatBubbleComponent.IsCommunityMessage, chatBubbleComponent.CommunityName);

chatBubbleComponent.IsDirty = false;

if (chatBubbleComponent.IsTranslationUpdate)
{
chatBubbleComponent.IsTranslationUpdate = false;

// Swap the text in place only while the bubble is still visible; never revive an already-hidden bubble.
if (nametagHolder.Nametag.ShowMessage)
nametagHolder.Nametag.MessageText = chatBubbleComponent.ChatMessage;

return;
}

nametagHolder.Nametag.DisplayMessage(chatBubbleComponent.ChatMessage, chatBubbleComponent.IsMention, chatBubbleComponent.IsPrivateMessage, chatBubbleComponent.IsOwnMessage, chatBubbleComponent.RecipientValidatedName, chatBubbleComponent.RecipientWalletId, chatBubbleComponent.RecipientNameColor, chatBubbleComponent.IsCommunityMessage, chatBubbleComponent.CommunityName);
}

[Query]
Expand Down
4 changes: 3 additions & 1 deletion Explorer/Assets/DCL/PluginSystem/Global/ChatPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,9 @@ public async UniTask InitializeAsync(ChatPluginSettings settings, CancellationTo
nametagsData,
settings.ChatSettingsAsset,
chatHistory,
communityDataService);
communityDataService,
chatEventBus,
translationSettings);

var currentChannelService = externalCurrentChannelService ?? new CurrentChannelService();

Expand Down
22 changes: 19 additions & 3 deletions Explorer/Assets/DCL/UIToolkit/DCLTextSettings.asset
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,38 @@ MonoBehaviour:
m_Version:
m_DefaultFontAsset: {fileID: 0}
m_DefaultFontAssetPath: Fonts & Materials/
m_FallbackFontAssets: []
m_FallbackFontAssets:
- {fileID: 11400000, guid: 4d0381cbcc4aad846ab6f3156ecee770, type: 2}
- {fileID: 11400000, guid: f38dc3f6cf805594b86ad9a2f931897e, type: 2}
m_FallbackOSFontAssets: []
m_MatchMaterialPreset: 0
m_MissingCharacterUnicode: 0
m_ClearDynamicDataOnBuild: 1
m_EnableEmojiSupport: 0
m_EmojiFallbackTextAssets: []
m_EmojiFallbackTextAssets:
- {fileID: 11400000, guid: 6e91b23f4d82c4cbfb4001ba83a92072, type: 2}
m_DefaultSpriteAsset: {fileID: 11400000, guid: 6e91b23f4d82c4cbfb4001ba83a92072, type: 2}
m_DefaultSpriteAssetPath: Sprite Assets/
m_FallbackSpriteAssets: []
m_MissingSpriteCharacterUnicode: 0
m_DefaultStyleSheet: {fileID: 0}
m_StyleSheetsResourcePath: Text Style Sheets/
m_DefaultColorGradientPresetsPath: Text Color Gradients/
m_UnicodeLineBreakingRules:
m_UnicodeLineBreakingRules: {fileID: 0}
m_LeadingCharacters: {fileID: 4900000, guid: d82c1b31c7e74239bff1220585707d2b, type: 3}
m_FollowingCharacters: {fileID: 4900000, guid: fade42e8bc714b018fac513c043d323b, type: 3}
m_UseModernHangulLineBreakingRules: 0
m_DisplayWarnings: 0
m_FontReferences:
- font: {fileID: 12800000, guid: 16e3d6ebbb2afa8478140aa1fffd9d09, type: 3}
fontAsset: {fileID: 0}
- font: {fileID: 12800000, guid: 05befaedcb2db4303a48d552554db836, type: 3}
fontAsset: {fileID: 0}
- font: {fileID: 12800000, guid: 6662efddf575d46528acad7d7309708b, type: 3}
fontAsset: {fileID: 0}
- font: {fileID: 12800000, guid: 3d5604d303a734a85bf5000c0e2e5d92, type: 3}
fontAsset: {fileID: 0}
- font: {fileID: 2230732570650464555, guid: 10e86a2123940e44b95c43e0bbfb27d1, type: 3}
fontAsset: {fileID: 0}
- font: {fileID: 2230732570650464555, guid: 4deb06e376f984b6790130c58914a876, type: 3}
fontAsset: {fileID: 0}
Loading
Loading