mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-08-11 14:02:05 +00:00
Add support for base text formats for Translatable
fixes #5342 and various other issues related to formatting codes in user input
This commit is contained in:
parent
48b80ecf78
commit
c4fb8832fe
@ -123,7 +123,7 @@ abstract class Command{
|
||||
}
|
||||
|
||||
if($this->permissionMessage === null){
|
||||
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->prefix(TextFormat::RED));
|
||||
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->baseTextFormat(TextFormat::RED));
|
||||
}elseif($this->permissionMessage !== ""){
|
||||
$target->sendMessage(str_replace("<permission>", $permission ?? implode(";", $this->permission), $this->permissionMessage));
|
||||
}
|
||||
@ -237,7 +237,7 @@ abstract class Command{
|
||||
public static function broadcastCommandMessage(CommandSender $source, Translatable|string $message, bool $sendToSource = true) : void{
|
||||
$users = $source->getServer()->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
|
||||
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
||||
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
$colored = $result->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC);
|
||||
|
||||
if($sendToSource){
|
||||
$source->sendMessage($message);
|
||||
|
@ -107,7 +107,7 @@ class FormattedCommandAlias extends Command{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
}else{
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED)));
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->baseTextFormat(TextFormat::RED)));
|
||||
|
||||
//to match the behaviour of SimpleCommandMap::dispatch()
|
||||
//this shouldn't normally happen, but might happen if the command was unregistered or modified after
|
||||
|
@ -226,7 +226,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->baseTextFormat(TextFormat::RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class ClearCommand extends VanillaCommand{
|
||||
}
|
||||
}catch(LegacyStringToItemParserException $e){
|
||||
//vanilla checks this at argument parsing layer, can't come up with a better alternative
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ class ClearCommand extends VanillaCommand{
|
||||
if($count > 0){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_testing($target->getName(), (string) $count));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -132,7 +132,7 @@ class ClearCommand extends VanillaCommand{
|
||||
if($clearedCount > 0){
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_clear_success($target->getName(), (string) $clearedCount));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -68,7 +68,7 @@ class EffectCommand extends VanillaCommand{
|
||||
|
||||
$effect = StringToEffectParser::getInstance()->parse($args[1]);
|
||||
if($effect === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ class GiveCommand extends VanillaCommand{
|
||||
try{
|
||||
$item = StringToItemParser::getInstance()->parse($args[1]) ?? LegacyStringToItemParser::getInstance()->parse($args[1]);
|
||||
}catch(LegacyStringToItemParserException $e){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ class GiveCommand extends VanillaCommand{
|
||||
$player->getInventory()->addItem($item);
|
||||
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_give_success(
|
||||
$item->getName() . " (" . $args[1] . ")",
|
||||
$item->getName() . TextFormat::RESET . " (" . $args[1] . ")",
|
||||
(string) $item->getCount(),
|
||||
$player->getName()
|
||||
));
|
||||
|
@ -105,22 +105,22 @@ class HelpCommand extends VanillaCommand{
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->prefix(TextFormat::GOLD));
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
|
||||
$usage = $cmd->getUsage();
|
||||
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
|
||||
->prefix(TextFormat::GOLD));
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
|
||||
$aliases = $cmd->getAliases();
|
||||
sort($aliases, SORT_NATURAL);
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
|
||||
->prefix(TextFormat::GOLD));
|
||||
->baseTextFormat(TextFormat::GOLD));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->baseTextFormat(TextFormat::RED));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class ParticleCommand extends VanillaCommand{
|
||||
$particle = $this->getParticle($name, $data);
|
||||
|
||||
if($particle === null){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->baseTextFormat(TextFormat::RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class SayCommand extends VanillaCommand{
|
||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_announcement(
|
||||
$sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()),
|
||||
implode(" ", $args)
|
||||
)->prefix(TextFormat::LIGHT_PURPLE));
|
||||
)->baseTextFormat(TextFormat::LIGHT_PURPLE));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,9 @@ class TellCommand extends VanillaCommand{
|
||||
|
||||
if($player instanceof Player){
|
||||
$message = implode(" ", $args);
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$name = $sender instanceof Player ? $sender->getDisplayName() : $sender->getName();
|
||||
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message), false);
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound());
|
||||
|
@ -99,11 +99,11 @@ abstract class VanillaCommand extends Command{
|
||||
|
||||
$v = (int) $input;
|
||||
if($v > $max){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->baseTextFormat(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
if($v < $min){
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->baseTextFormat(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_filter;
|
||||
@ -141,18 +142,26 @@ class Language{
|
||||
/**
|
||||
* @param (float|int|string|Translatable)[] $params
|
||||
*/
|
||||
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{
|
||||
$baseText = ($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $this->internalGet($str) : null;
|
||||
if($baseText === null){ //key not found, embedded inside format string, or doesn't match prefix
|
||||
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null, string $baseFormat = "") : string{
|
||||
if($onlyPrefix !== null && !str_starts_with($str, $onlyPrefix)){
|
||||
//plain key for client-side translation
|
||||
//% is added here if we add base format since this will turn into an embedded key
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, "%" . $str) : $str;
|
||||
}
|
||||
$baseText = $this->internalGet($str);
|
||||
if($baseText === null){ //key not found, embedded inside format string with %, or doesn't match prefix
|
||||
$baseText = $this->parseTranslation($str, $onlyPrefix);
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($params) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
|
||||
if($baseFormat !== ""){
|
||||
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
|
||||
}
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
|
||||
return $baseText;
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
|
||||
}
|
||||
|
||||
public function translate(Translatable $c) : string{
|
||||
@ -161,12 +170,17 @@ class Language{
|
||||
$baseText = $this->parseTranslation($c->getText());
|
||||
}
|
||||
|
||||
$baseFormat = $c->getBaseFormat();
|
||||
|
||||
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
|
||||
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
|
||||
if($baseFormat !== ""){
|
||||
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
|
||||
}
|
||||
$baseText = str_replace("{%$i}", $replacement, $baseText);
|
||||
}
|
||||
|
||||
return $baseText;
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
|
||||
}
|
||||
|
||||
protected function internalGet(string $id) : ?string{
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\lang;
|
||||
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
final class Translatable{
|
||||
@ -34,7 +35,8 @@ final class Translatable{
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $text,
|
||||
array $params = []
|
||||
array $params = [],
|
||||
private string $baseFormat = ""
|
||||
){
|
||||
foreach(Utils::promoteKeys($params) as $k => $param){
|
||||
if(!($param instanceof Translatable)){
|
||||
@ -60,6 +62,8 @@ final class Translatable{
|
||||
return $this->params[$i] ?? null;
|
||||
}
|
||||
|
||||
public function getBaseFormat() : string{ return $this->baseFormat; }
|
||||
|
||||
public function format(string $before, string $after) : self{
|
||||
return new self("$before%$this->text$after", $this->params);
|
||||
}
|
||||
@ -71,4 +75,12 @@ final class Translatable{
|
||||
public function postfix(string $postfix) : self{
|
||||
return new self("%$this->text" . $postfix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base format to be applied to the translation result by {@link TextFormat::addBase()}.
|
||||
* Any existing base format is overwritten.
|
||||
*/
|
||||
public function baseTextFormat(string $baseFormat) : self{
|
||||
return new self($this->text, $this->params, $baseFormat);
|
||||
}
|
||||
}
|
||||
|
@ -767,7 +767,7 @@ class NetworkSession{
|
||||
$errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
|
||||
|
||||
$this->disconnect(
|
||||
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->prefix(TextFormat::RED),
|
||||
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->baseTextFormat(TextFormat::RED),
|
||||
disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error($disconnectScreenMessage ?? $reason, $errorId),
|
||||
);
|
||||
}
|
||||
@ -1130,8 +1130,12 @@ class NetworkSession{
|
||||
public function prepareClientTranslatableMessage(Translatable $message) : array{
|
||||
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
|
||||
$language = $this->player->getLanguage();
|
||||
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters());
|
||||
return [$language->translateString($message->getText(), $parameters, "pocketmine."), $parameters];
|
||||
$baseFormat = $message->getBaseFormat();
|
||||
$parameters = array_map(function(string|Translatable $p) use ($baseFormat, $language){
|
||||
$string = $p instanceof Translatable ? $language->translate($p) : $p;
|
||||
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $string) . TextFormat::RESET : $string;
|
||||
}, $message->getParameters());
|
||||
return [$language->translateString($message->getText(), $parameters, "pocketmine.", $baseFormat), $parameters];
|
||||
}
|
||||
|
||||
public function onChatMessage(Translatable|string $message) : void{
|
||||
|
@ -411,7 +411,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
public function getLeaveMessage() : Translatable|string{
|
||||
if($this->spawned){
|
||||
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->prefix(TextFormat::YELLOW);
|
||||
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW);
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -946,7 +946,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
});
|
||||
|
||||
$ev = new PlayerJoinEvent($this,
|
||||
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->prefix(TextFormat::YELLOW)
|
||||
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW)
|
||||
);
|
||||
$ev->call();
|
||||
if($ev->getJoinMessage() !== ""){
|
||||
|
@ -190,8 +190,10 @@ abstract class TextFormat{
|
||||
* - Base format "§c" (red) + "Hello" (no format) = "§r§cHello"
|
||||
* - Base format "§c" + "Hello §rWorld" = "§r§cHello §r§cWorld"
|
||||
*
|
||||
* Note: Adding base formatting to the output string a second time will result in a combination of formats from both
|
||||
* calls. This is not by design, but simply a consequence of the way the function is implemented.
|
||||
* Note: Adding base formatting to the output string a second time won't override conflicting formatting from the
|
||||
* earlier call (e.g. adding base format BLUE to a string which already has YELLOW base formatting will
|
||||
* still result in yellow text after any RESET code). However, complementary codes (e.g. italic, bold) will combine
|
||||
* with the existing codes (e.g. adding ITALIC to a string with base format YELLOW will give yellow & italic text).
|
||||
*/
|
||||
public static function addBase(string $baseFormat, string $string) : string{
|
||||
$baseFormatParts = self::tokenize($baseFormat);
|
||||
|
Loading…
x
Reference in New Issue
Block a user