Skip to content

Commit

Permalink
Add method to get bungee translation with component argument support
Browse files Browse the repository at this point in the history
Also:
- Output arguments on missing bungee translation fallback
- Fix BaseComponent legacy reset value not copied in copy-constructor

(cherry picked from commit 9ec0d52)
  • Loading branch information
Janmm14 committed Apr 21, 2024
1 parent ee02d98 commit 912bc88
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 7 deletions.
9 changes: 9 additions & 0 deletions api/src/main/java/net/md_5/bungee/api/ProxyServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ public static void setInstance(ProxyServer instance)
*/
public abstract String getTranslation(String name, Object... args);

/**
* Gets a localized string from the .properties file.
*
* @param name translation name
* @param args translation arguments
* @return the localized string
*/
public abstract BaseComponent getTranslationComponent(String name, Object... args);

/**
* Gets the main logger which can be used as a suitable replacement for
* {@link System#out} and {@link System#err}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public BaseComponent()
addExtra( extra.duplicate() );
}
}
setReset( old.isReset() );
}

/**
Expand Down
19 changes: 13 additions & 6 deletions proxy/src/main/java/net/md_5/bungee/BungeeCord.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.text.Format;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
Expand Down Expand Up @@ -98,6 +99,7 @@
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.query.RemoteQuery;
import net.md_5.bungee.scheduler.BungeeScheduler;
import net.md_5.bungee.util.BungeeTranslationUtil;
import net.md_5.bungee.util.CaseInsensitiveMap;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.impl.JDK14LoggerFactory;
Expand All @@ -120,7 +122,7 @@ public class BungeeCord extends ProxyServer
/**
* Localization formats.
*/
private Map<String, Format> messageFormats;
private Map<String, MessageFormat> messageFormats;
public EventLoopGroup eventLoops;
/**
* locations.yml save thread.
Expand Down Expand Up @@ -552,7 +554,7 @@ public String getVersion()

public final void reloadMessages()
{
Map<String, Format> cachedFormats = new HashMap<>();
Map<String, MessageFormat> cachedFormats = new HashMap<>();

File file = new File( "messages.properties" );
if ( file.isFile() )
Expand All @@ -579,7 +581,7 @@ public final void reloadMessages()
messageFormats = Collections.unmodifiableMap( cachedFormats );
}

private void cacheResourceBundle(Map<String, Format> map, ResourceBundle resourceBundle)
private static void cacheResourceBundle(Map<String, MessageFormat> map, ResourceBundle resourceBundle)
{
Enumeration<String> keys = resourceBundle.getKeys();
while ( keys.hasMoreElements() )
Expand All @@ -592,17 +594,22 @@ private void cacheResourceBundle(Map<String, Format> map, ResourceBundle resourc
public String getTranslation(String name, Object... args)
{
Format format = messageFormats.get( name );
return ( format != null ) ? format.format( args ) : "<translation '" + name + "' missing>";
return ( format != null ) ? format.format( args ) : "<translation '" + name + "' missing, args=" + Arrays.toString( args ) + ">";
}

@Override
public BaseComponent getTranslationComponent(String name, Object... args)
{
return BungeeTranslationUtil.getTranslationComponent0( messageFormats.get( name ).toPattern(), args );
}

@Override
@SuppressWarnings("unchecked")
public Collection<ProxiedPlayer> getPlayers()
{
connectionLock.readLock().lock();
try
{
return Collections.unmodifiableCollection( new HashSet( connections.values() ) );
return Collections.unmodifiableCollection( new HashSet<>( connections.values() ) );
} finally
{
connectionLock.readLock().unlock();
Expand Down
2 changes: 1 addition & 1 deletion proxy/src/main/java/net/md_5/bungee/ServerConnector.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public void handle(Kick kick) throws Exception
throw CancelSendSignal.INSTANCE;
}

String message = bungee.getTranslation( "connect_kick", target.getName(), event.getKickReason() );
BaseComponent message = bungee.getTranslationComponent( "connect_kick", target.getName(), event.getReason() );
if ( user.isDimensionChange() )
{
user.disconnect( message );
Expand Down
146 changes: 146 additions & 0 deletions proxy/src/main/java/net/md_5/bungee/util/BungeeTranslationUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package net.md_5.bungee.util;

import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.jetbrains.annotations.NotNull;

public class BungeeTranslationUtil
{
// support only simple translations, rip escaping and specialized formatting
private static final Pattern TRANSLATE_FORMAT = Pattern.compile( "\\{(\\d*)(?:,[^}]+)?}" );

public static @NotNull BaseComponent getTranslationComponent0(String trans, Object... args)
{

Matcher matcher = TRANSLATE_FORMAT.matcher( trans );
int position = 0;
int i = 0;
BaseComponent lastNonArg = null;
ComponentBuilder builder = new ComponentBuilder();
while ( matcher.find() )
{
// Add the static part of the string
int pos = matcher.start();
if ( pos != position )
{
builder.appendLegacy( trans.substring( position, pos ) );
BaseComponent curr = builder.getCurrentComponent();
if ( lastNonArg != null )
{
// undo eventual formatting of previous argument
boolean colorChange = curr.isReset();
if ( !Objects.equals( lastNonArg.getColor(), curr.getColor() ) )
{
colorChange = true;
builder.color( lastNonArg.getColor() );
}
if ( colorChange && lastNonArg.isBold() || lastNonArg.isBold() != curr.isBold() )
{
builder.bold( lastNonArg.isBold() );
}
if ( colorChange && lastNonArg.isItalic() || lastNonArg.isItalic() != curr.isItalic() )
{
builder.italic( lastNonArg.isItalic() );
}
if ( colorChange && lastNonArg.isUnderlined() || lastNonArg.isUnderlined() != curr.isUnderlined() )
{
builder.underlined( lastNonArg.isUnderlined() );
}
if ( colorChange && lastNonArg.isStrikethrough() || lastNonArg.isStrikethrough() != curr.isStrikethrough() )
{
builder.strikethrough( lastNonArg.isStrikethrough() );
}
if ( colorChange && lastNonArg.isObfuscated() || lastNonArg.isObfuscated() != curr.isObfuscated() )
{
builder.obfuscated( lastNonArg.isObfuscated() );
}
}
lastNonArg = findLastComponent( builder.getCurrentComponent().duplicate() );
}
position = matcher.end();

// find argument index
String withIndex = matcher.group( 1 );
Object arg;
if ( !withIndex.isEmpty() )
{
int index = Integer.parseInt( withIndex );
if ( index >= 0 && index < args.length )
{
arg = args[ index ];
} else
{
arg = "{" + withIndex + "}";
}
} else if ( i < args.length )
{
arg = args[ i++ ];
} else
{
arg = "{" + withIndex + "}";
}
if ( arg instanceof BaseComponent[] )
{
arg = TextComponent.fromArray( (BaseComponent[]) arg );
}
if ( arg instanceof BaseComponent )
{
BaseComponent argB = (BaseComponent) arg;
// argument base formatting should use formatting of last non-argument part if no reset is requested
builder.append( argB, isFirstReset( argB ) ? ComponentBuilder.FormatRetention.NONE : ComponentBuilder.FormatRetention.ALL );
} else
{
builder.append( arg.toString() );
}
}
return builder.build();
}

private static BaseComponent findLastComponent(BaseComponent comp)
{
BaseComponent last = comp;
List<BaseComponent> extra = comp.getExtra();
if ( extra != null && !extra.isEmpty() )
{
last = findLastComponent( extra.get( extra.size() - 1 ) );
}
return last;
}

private static boolean isFirstReset(BaseComponent comp)
{
System.out.println( "isFirstReset(comp = " + comp + ")" );
if ( comp.isReset() )
{
return true;
}
if ( isSelfEmpty( comp ) )
{
List<BaseComponent> extra = comp.getExtra();
if ( extra != null && !extra.isEmpty() )
{
return isFirstReset( extra.get( 0 ) );
}
}
return false;
}

private static boolean isSelfEmpty(BaseComponent comp)
{
if ( comp instanceof TextComponent )
{
return ( (TextComponent) comp ).getText().isEmpty();
}
if ( comp instanceof TranslatableComponent )
{
return ( (TranslatableComponent) comp ).getTranslate().isEmpty();
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.md_5.bungee.util;

import static org.junit.jupiter.api.Assertions.*;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.junit.jupiter.api.Test;

public class BungeeTranslationUtilTest
{

private static final String TEST_TRANSLATION = ChatColor.RED + "Kicked whilst connecting to " + ChatColor.BOLD + "{0}: {1}";

@Test
public void test()
{
BaseComponent component = BungeeTranslationUtil.getTranslationComponent0( TEST_TRANSLATION,
new ComponentBuilder( "lobby" ).color( ChatColor.AQUA ).build(), "Test-Kick" );
assertEquals( "{\"extra\":[{\"color\":\"red\",\"text\":\"Kicked whilst connecting to \"},"
+ "{\"bold\":true,\"color\":\"red\",\"text\":\"\"},"
+ "{\"bold\":true,\"color\":\"red\",\"extra\":[{\"color\":\"aqua\",\"text\":\"lobby\"}],\"text\":\"\"},"
+ "{\"bold\":true,\"color\":\"red\",\"text\":\": \"},"
+ "{\"bold\":true,\"color\":\"red\",\"text\":\"Test-Kick\"}],"
+ "\"text\":\"\"}", ComponentSerializer.toString( component ) );
}

@Test
public void testLegacyArgumentReset()
{
BaseComponent component = BungeeTranslationUtil.getTranslationComponent0( TEST_TRANSLATION,
TextComponent.fromLegacy( ChatColor.AQUA + "lobby" ), "Test-Kick" );
assertEquals( "{\"extra\":[{\"color\":\"red\",\"text\":\"Kicked whilst connecting to \"},"
+ "{\"bold\":true,\"color\":\"red\",\"text\":\"\"},"
+ "{\"extra\":[{\"color\":\"aqua\",\"text\":\"lobby\"}],\"text\":\"\"}," // argument 0 not bold unlike above
+ "{\"bold\":true,\"color\":\"red\",\"text\":\": \"},"
+ "{\"bold\":true,\"color\":\"red\",\"text\":\"Test-Kick\"}],\""
+ "text\":\"\"}", ComponentSerializer.toString( component ) );
}
}

0 comments on commit 912bc88

Please sign in to comment.