diff --git a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java index bb07f1c66..d90542e1c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java +++ b/common/src/main/java/me/lucko/luckperms/common/actionlog/LoggedAction.java @@ -339,7 +339,7 @@ public class LoggedAction implements Action { parts.add(context.getKey() + "=" + context.getValue()); } } else if (o instanceof Duration) { - parts.add(DurationFormatter.CONCISE.format((Duration) o)); + parts.add(DurationFormatter.CONCISE.formatString((Duration) o)); } else { parts.add(String.valueOf(o)); } diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java index b2c71cab8..8f3ee18b2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/Message.java @@ -776,7 +776,7 @@ public interface Message { .key("luckperms.command.generic.info.expires-in") .color(GRAY) .append(space()) - .append(text(DurationFormatter.LONG.format(node.getExpiryDuration()))) + .append(DurationFormatter.LONG.format(node.getExpiryDuration())) ) .append(CLOSE_BRACKET) ); @@ -820,7 +820,7 @@ public interface Message { .key("luckperms.command.generic.info.expires-in") .color(GRAY) .append(space()) - .append(text(DurationFormatter.LONG.format(node.getExpiryDuration()))) + .append(DurationFormatter.LONG.format(node.getExpiryDuration())) ) .append(CLOSE_BRACKET) ); @@ -966,7 +966,7 @@ public interface Message { builder.append(text() .color(GRAY) .append(OPEN_BRACKET) - .append(text(DurationFormatter.CONCISE.format(node.getExpiryDuration()))) + .append(DurationFormatter.CONCISE.format(node.getExpiryDuration())) .append(CLOSE_BRACKET) ); } @@ -1456,7 +1456,7 @@ public interface Message { .append(text(" ")) .append(translatable("luckperms.command.info.uptime-key")) .append(text(": ")) - .append(text(DurationFormatter.CONCISE_LOW_ACCURACY.format(Duration.between(plugin.getBootstrap().getStartupTime(), Instant.now())), GRAY))), + .append(text().color(GRAY).append(DurationFormatter.CONCISE_LOW_ACCURACY.format(Duration.between(plugin.getBootstrap().getStartupTime(), Instant.now()))))), prefixed(text() .color(DARK_AQUA) .append(text(" ")) @@ -1621,7 +1621,7 @@ public interface Message { .append(text("- ")) .append(translatable("luckperms.command.generic.info.expires-in")) .append(space()) - .append(text(DurationFormatter.LONG.format(node.getExpiryDuration()))) + .append(DurationFormatter.LONG.format(node.getExpiryDuration())) .build() ); @@ -1726,7 +1726,7 @@ public interface Message { .append(text("- ")) .append(translatable("luckperms.command.generic.info.expires-in")) .append(space()) - .append(text(DurationFormatter.LONG.format(node.getExpiryDuration()))) + .append(DurationFormatter.LONG.format(node.getExpiryDuration())) .build() ); @@ -1822,7 +1822,7 @@ public interface Message { text(permission, AQUA), text(value, AQUA), text().color(AQUA).append(holder.getFormattedDisplayName()), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context) ) .append(FULL_STOP) @@ -1884,9 +1884,9 @@ public interface Message { text(permission, AQUA), text(value, AQUA), text().color(AQUA).append(holder.getFormattedDisplayName()), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context), - text(DurationFormatter.LONG.format(durationLess), AQUA) + text().color(AQUA).append(DurationFormatter.LONG.format(durationLess)) ) .append(FULL_STOP) ); @@ -1922,7 +1922,7 @@ public interface Message { .args( text().color(AQUA).append(holder.getFormattedDisplayName()), text().color(AQUA).append(parent.getFormattedDisplayName()), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context) ) .append(FULL_STOP) @@ -1984,9 +1984,9 @@ public interface Message { .args( text().color(AQUA).append(holder.getFormattedDisplayName()), text().color(AQUA).append(parent), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context), - text(DurationFormatter.LONG.format(durationLess), AQUA) + text().color(AQUA).append(DurationFormatter.LONG.format(durationLess)) ) .append(FULL_STOP) ); @@ -2155,7 +2155,7 @@ public interface Message { .key("luckperms.command.generic.info.expires-in") .color(GRAY) .append(space()) - .append(text(DurationFormatter.CONCISE.format(node.getExpiryDuration()), AQUA)) + .append(text().color(AQUA).append(DurationFormatter.CONCISE.format(node.getExpiryDuration()))) ) .append(CLOSE_BRACKET) ); @@ -2223,7 +2223,7 @@ public interface Message { .key("luckperms.command.generic.info.expires-in") .color(GRAY) .append(space()) - .append(text(DurationFormatter.CONCISE.format(node.getExpiryDuration()), AQUA)) + .append(text().color(AQUA).append(DurationFormatter.CONCISE.format(node.getExpiryDuration()))) ) .append(CLOSE_BRACKET) ); @@ -2388,7 +2388,7 @@ public interface Message { text(type.toString()), text().color(WHITE).append(text('\'')).append(formatColoredValue(value)).append(text('\'')), text(priority, AQUA), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context) ) .append(FULL_STOP) @@ -2519,7 +2519,7 @@ public interface Message { text().color(WHITE).append(text('\'')).append(text(key)).append(text('\'')), text().color(WHITE).append(text('\'')).append(formatColoredValue(value)).append(text('\'')), text().color(AQUA).append(holder.getFormattedDisplayName()), - text(DurationFormatter.LONG.format(duration), AQUA), + text().color(AQUA).append(DurationFormatter.LONG.format(duration)), formatContextSet(context) ) .append(FULL_STOP) @@ -2927,7 +2927,7 @@ public interface Message { .append(text(" - ")) .append(translatable("luckperms.command.generic.info.expires-in")) .append(space()) - .append(text(DurationFormatter.LONG.format(node.getExpiryDuration())))) + .append(DurationFormatter.LONG.format(node.getExpiryDuration()))) ); Args0 USER_REMOVEGROUP_ERROR_PRIMARY = () -> prefixed(translatable() @@ -3385,9 +3385,10 @@ public interface Message { .append(text() .color(DARK_GRAY) .append(OPEN_BRACKET) - .append(text() - .content(DurationFormatter.CONCISE_LOW_ACCURACY.format(action.getDurationSince()) + " ago") + .append(translatable() .color(GRAY) + .key("luckperms.duration.since") + .args(DurationFormatter.CONCISE_LOW_ACCURACY.format(action.getDurationSince())) ) .append(CLOSE_BRACKET) ) diff --git a/common/src/main/java/me/lucko/luckperms/common/util/DurationFormatter.java b/common/src/main/java/me/lucko/luckperms/common/util/DurationFormatter.java index f4d9c6c40..0e90e881e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/util/DurationFormatter.java +++ b/common/src/main/java/me/lucko/luckperms/common/util/DurationFormatter.java @@ -25,54 +25,43 @@ package me.lucko.luckperms.common.util; +import me.lucko.luckperms.common.locale.TranslationManager; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; + import java.time.Duration; import java.time.temporal.ChronoUnit; /** * Formats durations to a readable form */ -public enum DurationFormatter { - LONG, - CONCISE { - @Override - protected String formatUnitPlural(ChronoUnit unit) { - return String.valueOf(Character.toLowerCase(unit.name().charAt(0))); - } +public class DurationFormatter { + public static final DurationFormatter LONG = new DurationFormatter(false); + public static final DurationFormatter CONCISE = new DurationFormatter(true); + public static final DurationFormatter CONCISE_LOW_ACCURACY = new DurationFormatter(true, 3); - @Override - protected String formatUnitSingular(ChronoUnit unit) { - return formatUnitPlural(unit); - } - }, - CONCISE_LOW_ACCURACY(3) { - @Override - protected String formatUnitPlural(ChronoUnit unit) { - return String.valueOf(Character.toLowerCase(unit.name().charAt(0))); - } - - @Override - protected String formatUnitSingular(ChronoUnit unit) { - return formatUnitPlural(unit); - } - }; - - private final Unit[] units = new Unit[]{ - new Unit(ChronoUnit.YEARS), - new Unit(ChronoUnit.MONTHS), - new Unit(ChronoUnit.WEEKS), - new Unit(ChronoUnit.DAYS), - new Unit(ChronoUnit.HOURS), - new Unit(ChronoUnit.MINUTES), - new Unit(ChronoUnit.SECONDS) + private static final ChronoUnit[] UNITS = new ChronoUnit[]{ + ChronoUnit.YEARS, + ChronoUnit.MONTHS, + ChronoUnit.WEEKS, + ChronoUnit.DAYS, + ChronoUnit.HOURS, + ChronoUnit.MINUTES, + ChronoUnit.SECONDS }; + private final boolean concise; private final int accuracy; - DurationFormatter() { - this(Integer.MAX_VALUE); + public DurationFormatter(boolean concise) { + this(concise, Integer.MAX_VALUE); } - DurationFormatter(int accuracy) { + public DurationFormatter(boolean concise, int accuracy) { + this.concise = concise; this.accuracy = accuracy; } @@ -82,16 +71,29 @@ public enum DurationFormatter { * @param duration the duration * @return the formatted string */ - public String format(Duration duration) { + public String formatString(Duration duration) { + return PlainComponentSerializer.plain().serialize(TranslationManager.render(format(duration))); + } + + /** + * Formats {@code duration} as a {@link Component}. + * + * @param duration the duration + * @return the formatted component + */ + public Component format(Duration duration) { long seconds = duration.getSeconds(); - StringBuilder output = new StringBuilder(); + TextComponent.Builder builder = Component.text(); int outputSize = 0; - for (Unit unit : this.units) { - long n = seconds / unit.duration; + for (ChronoUnit unit : UNITS) { + long n = seconds / unit.getDuration().getSeconds(); if (n > 0) { - seconds -= unit.duration * n; - output.append(' ').append(n).append(unit.toString(n)); + seconds -= unit.getDuration().getSeconds() * n; + if (outputSize != 0) { + builder.append(Component.space()); + } + builder.append(formatPart(n, unit)); outputSize++; } if (seconds <= 0 || outputSize >= this.accuracy) { @@ -99,35 +101,22 @@ public enum DurationFormatter { } } - if (output.length() == 0) { - return "0" + this.units[this.units.length - 1].stringPlural; + if (outputSize == 0) { + return formatPart(0, ChronoUnit.SECONDS); } - return output.substring(1); + return builder.build(); } - protected String formatUnitPlural(ChronoUnit unit) { - return " " + unit.name().toLowerCase(); - } + // Translation keys are in the format: + // luckperms.duration.unit.years.plural={0} years + // luckperms.duration.unit.years.singular={0} year + // luckperms.duration.unit.years.short={0}y + // ... and so on - protected String formatUnitSingular(ChronoUnit unit) { - String s = unit.name().toLowerCase(); - return " " + s.substring(0, s.length() - 1); - } - - private final class Unit { - private final long duration; - private final String stringPlural; - private final String stringSingular; - - Unit(ChronoUnit unit) { - this.duration = unit.getDuration().getSeconds(); - this.stringPlural = formatUnitPlural(unit); - this.stringSingular = formatUnitSingular(unit); - } - - public String toString(long n) { - return n == 1 ? this.stringSingular : this.stringPlural; - } + private TranslatableComponent formatPart(long amount, ChronoUnit unit) { + String format = this.concise ? "short" : amount == 1 ? "singular" : "plural"; + String translationKey = "luckperms.duration.unit." + unit.name().toLowerCase() + "." + format; + return Component.translatable(translationKey, Component.text(amount)); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java index 5fd5028b0..a0fada13e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java +++ b/common/src/main/java/me/lucko/luckperms/common/verbose/VerboseListener.java @@ -284,7 +284,7 @@ public class VerboseListener { // retrieve variables String startDate = DATE_FORMAT.format(this.startTime); String endDate = DATE_FORMAT.format(Instant.now()); - String duration = DurationFormatter.CONCISE.format(Duration.between(this.startTime, Instant.now())); + String duration = DurationFormatter.CONCISE.formatString(Duration.between(this.startTime, Instant.now())); boolean truncated = this.matchedCounter.get() > this.results.size(); JObject metadata = new JObject() diff --git a/common/src/main/resources/luckperms_en.properties b/common/src/main/resources/luckperms_en.properties index 0262e7fa5..3ec892a06 100644 --- a/common/src/main/resources/luckperms_en.properties +++ b/common/src/main/resources/luckperms_en.properties @@ -24,6 +24,28 @@ luckperms.login.craftbukkit-offline-mode-error=this is likely due to a conflict luckperms.login.unexpected-error=An unexpected error occurred whilst setting up your permissions data luckperms.opsystem.disabled=The vanilla OP system is disabled on this server luckperms.opsystem.sponge-warning=Please note that Server Operator status has no effect on Sponge permission checks when a permission plugin is installed, you must edit user data directly +luckperms.duration.unit.years.plural={0} years +luckperms.duration.unit.years.singular={0} year +luckperms.duration.unit.years.short={0}y +luckperms.duration.unit.months.plural={0} months +luckperms.duration.unit.months.singular={0} month +luckperms.duration.unit.months.short={0}mo +luckperms.duration.unit.weeks.plural={0} weeks +luckperms.duration.unit.weeks.singular={0} week +luckperms.duration.unit.weeks.short={0}w +luckperms.duration.unit.days.plural={0} days +luckperms.duration.unit.days.singular={0} day +luckperms.duration.unit.days.short={0}d +luckperms.duration.unit.hours.plural={0} hours +luckperms.duration.unit.hours.singular={0} hour +luckperms.duration.unit.hours.short={0}h +luckperms.duration.unit.minutes.plural={0} minutes +luckperms.duration.unit.minutes.singular={0} minute +luckperms.duration.unit.minutes.short={0}m +luckperms.duration.unit.seconds.plural={0} seconds +luckperms.duration.unit.seconds.singular={0} second +luckperms.duration.unit.seconds.short={0}s +luckperms.duration.since={0} ago luckperms.command.misc.invalid-code=Invalid code luckperms.command.misc.response-code-key=response code luckperms.command.misc.error-message-key=message