Skip to content

Commit

Permalink
editorconfig for clean code - no new version
Browse files Browse the repository at this point in the history
  • Loading branch information
CubBossa committed Jan 27, 2024
1 parent bb9dc0b commit ad8adf4
Show file tree
Hide file tree
Showing 72 changed files with 4,216 additions and 2,969 deletions.
1,209 changes: 1,209 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

183 changes: 111 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
# TinyTranslations

A translation framework to translate chat messages.
This framework builds upon [Kyori Components and the MiniMessage format](https://docs.adventure.kyori.net/minimessage/format.html).
This framework builds
upon [Kyori Components and the MiniMessage format](https://docs.adventure.kyori.net/minimessage/format.html).

## Wiki

- [Overview](https://github.com/CubBossa/Translations/wiki)
- [Setup](https://github.com/CubBossa/Translations#maven)
- Messages
- [Overview](https://github.com/CubBossa/Translations/wiki/Messages)
- [Using Placeholders](https://github.com/CubBossa/Translations/wiki/Placeholders)
- [Send or Use Messages](https://github.com/CubBossa/Translations/wiki/Send-or-Use-Messages)
- [Alternative Chat Formats](https://github.com/CubBossa/Translations/wiki/Legacy-Format-Support)
- [Per Player Locale](https://github.com/CubBossa/Translations/wiki/Per-Player-Locale)
- [Overview](https://github.com/CubBossa/Translations/wiki/Messages)
- [Using Placeholders](https://github.com/CubBossa/Translations/wiki/Placeholders)
- [Send or Use Messages](https://github.com/CubBossa/Translations/wiki/Send-or-Use-Messages)
- [Alternative Chat Formats](https://github.com/CubBossa/Translations/wiki/Legacy-Format-Support)
- [Per Player Locale](https://github.com/CubBossa/Translations/wiki/Per-Player-Locale)
- [Styles](https://github.com/CubBossa/Translations/wiki/Styles)
- Storages
- [Creating a Custom Storage](https://github.com/CubBossa/Translations/wiki/Creating-a--Custom-Storage)
- [YAML](https://github.com/CubBossa/Translations/wiki/Yaml-Storage)
- [Properties](https://github.com/CubBossa/Translations/wiki/Properties-Storage)
- [Creating a Custom Storage](https://github.com/CubBossa/Translations/wiki/Creating-a--Custom-Storage)
- [YAML](https://github.com/CubBossa/Translations/wiki/Yaml-Storage)
- [Properties](https://github.com/CubBossa/Translations/wiki/Properties-Storage)

## Maven

Install the following repository and dependency in your pom.xml or your build.gradle.
Make sure to use the latest version.

```XML

<repositories>
<repository>
<id>Translations</id>
Expand All @@ -33,6 +36,7 @@ Make sure to use the latest version.
```

```XML

<dependencies>
<dependency>
<groupId>de.cubbossa</groupId>
Expand All @@ -44,6 +48,7 @@ Make sure to use the latest version.
```

### Shading

When shading, it is highly recommended to relocate the resource within your plugin.
This assures that no other plugin loads outdated Translations classes before your
plugin can load the latest classes. Occurring errors would potentially disable your plugin on startup.
Expand Down Expand Up @@ -79,6 +84,7 @@ plugin can load the latest classes. Occurring errors would potentially disable y
```

or with gradle:

```groovy
tasks.shadowJar {
minimize()
Expand All @@ -90,32 +96,35 @@ tasks.shadowJar {

Your server must find and load the Kyori Adventure classes for Translations to work.
Either use `TinyTranslations-paper` to use the Adventure classes provided by paper or use
`TinyTranslations-bukkit` to include them as shaded dependencies.
`TinyTranslations-bukkit` to include them as shaded dependencies.

## Overview

Translations are split into global server-wide translations and local application translations.
Translations exist in a treelike structure.
The global root Translations instance allows to provide translation strings and styles for all following Translations instances.
The global root Translations instance allows to provide translation strings and styles for all following Translations
instances.
Each Translation instance can be forked into a child, which then uses all styles and messages of its parent but has some
encapsulated translations on its own. Mostly, there will be one global Translations instance and one per plugin.

Example of the Server folder structure and how translations are included:

```YML
/Server
/plugins
/lang
global_styles.properties # <--- global styling rules
en-US.properties # <--- global messages (like the server name)
/YourPlugin
/lang
styles.properties # <--- application only styles
en-US.properties # <--- application only messages
/plugins

/lang
global_styles.properties # <--- global styling rules
en-US.properties # <--- global messages (like the server name)

/YourPlugin
/lang
styles.properties # <--- application only styles
en-US.properties # <--- application only messages
```

Styles are a way to create new tag resolvers

```properties
# We use opening tags to define simple styles.
text-light="<white>"
Expand All @@ -134,6 +143,7 @@ url="<blue><u><click:open_url:"{slot}"><hover:show_text:"Click to open url"><sho

Messages can be stored in many ways, like SQL, Yaml or properties.
In a properties file, messages would look like this:

```properties
some.example.message="<text-light>Some light text <aqua>that can also be styled directly</aqua></text-light>"
some.example.reference="An embedded message: {msg:some.example.message}"
Expand All @@ -142,12 +152,14 @@ some.example.reference="An embedded message: {msg:some.example.message}"
As you can see in the example, messages can be embedded into each other, which
allows you to simply create own messages and use them all over the place.
Why is this useful? Think of the following example from my plugin:

```properties
# c-brand is a style for the main plugin color.
# bg and bg-dark are global styles.
prefix="<primary>PathFinder </primary><bg_dark>| </bg_dark><bg>"
other.message="{msg:prefix}Hello."
```

Prefix is not a message that is enforced by the plugin.
Users can simply create the entry, and it will be loaded by the plugin and
embedded in other messages.
Expand All @@ -158,52 +170,52 @@ embedded in other messages.
import de.cubbossa.tinytranslations.MessageBuilder;

class Messages {
public static final Message PREFIX = new MessageBuilder("prefix")
.withDefault("<gradient:#ff0000:#ffff00:#ff0000>My Awesome Plugin</gradient>")
.build();
public static final Message NO_PERM = new MessageBuilder("no_perm")
.withDefault("<prefix_negative>No permissions!</prefix_negative>")
.build();
public static final Message PREFIX = new MessageBuilder("prefix")
.withDefault("<gradient:#ff0000:#ffff00:#ff0000>My Awesome Plugin</gradient>")
.build();
public static final Message NO_PERM = new MessageBuilder("no_perm")
.withDefault("<prefix_negative>No permissions!</prefix_negative>")
.build();
}


class ExamplePlugin extends JavaPlugin {

Translations translations;

public void onEnable() {
// create a Translations instance for your plugin
translations = BukkitTinyTranslations.application(this);

// define the storage types for your plugins locale
translations.setMessageStorage(new PropertiesMessageStorage(getLogger(), new File(getDataFolder(), "/lang/")));
translations.setStyleStorage(new PropertiesStyleStorage(new File(getDataFolder(), "/lang/styles.properties")));

// register all your messages to your Translations instance
// a message cannot be translated without a Translations instance, which works as
// messageTranslator.
translations.addMessages(messageA, messageB, messageC);
translations.addMessage(messageD);
// just load all public static final messages declared in Messages.class
translations.addMessages(TinyTranslations.messageFieldsFromClass(Messages.class));

// They will not overwrite pre-existing values.
// You only need to save values that you assigned programmatically, like from a
// message builder. You can also create a de.properties resource and save it as file instead.
// Then there is no need to write the german defaults to file here.
translations.saveLocale(Locale.ENGLISH);
translations.saveLocale(Locale.GERMAN);

// load all styles and locales from file. This happens for all parent translations,
// so all changes to the global styles and translations will apply too.
translations.loadStyles();
translations.loadLocales();
}

public void onDisable() {
// close open Translations instance
translations.close();
}
Translations translations;

public void onEnable() {
// create a Translations instance for your plugin
translations = BukkitTinyTranslations.application(this);

// define the storage types for your plugins locale
translations.setMessageStorage(new PropertiesMessageStorage(getLogger(), new File(getDataFolder(), "/lang/")));
translations.setStyleStorage(new PropertiesStyleStorage(new File(getDataFolder(), "/lang/styles.properties")));

// register all your messages to your Translations instance
// a message cannot be translated without a Translations instance, which works as
// messageTranslator.
translations.addMessages(messageA, messageB, messageC);
translations.addMessage(messageD);
// just load all public static final messages declared in Messages.class
translations.addMessages(TinyTranslations.messageFieldsFromClass(Messages.class));

// They will not overwrite pre-existing values.
// You only need to save values that you assigned programmatically, like from a
// message builder. You can also create a de.properties resource and save it as file instead.
// Then there is no need to write the german defaults to file here.
translations.saveLocale(Locale.ENGLISH);
translations.saveLocale(Locale.GERMAN);

// load all styles and locales from file. This happens for all parent translations,
// so all changes to the global styles and translations will apply too.
translations.loadStyles();
translations.loadLocales();
}

public void onDisable() {
// close open Translations instance
translations.close();
}
}
```

Expand All @@ -214,6 +226,7 @@ Locale files can be generated from a class that holds Messages
as static members.

A fully defined message:

```Java
public static final Message ERR_NO_PLAYER = new MessageBuilder("error.must_be_player")
.withDefault("<prefix_negative>No player found: '{input}'.</prefix_negative>")
Expand All @@ -224,6 +237,7 @@ public static final Message ERR_NO_PLAYER = new MessageBuilder("error.must_be_pl
```

Or maybe just

```Java
public static final Message ERR_NO_PERM = new MessageBuilder("error.no_perm")
.withDefault("<prefix_negative>No permission!</prefix_negative>")
Expand All @@ -233,44 +247,69 @@ public static final Message ERR_NO_PERM = new MessageBuilder("error.no_perm")
### Add Messages to Translations

Don't forget to register all messages to your application!!

```Java
translations.addMessage(Messages.ERR_NO_PLAYER);
translations.addMessage(Messages.ERR_NO_PERM);
translations.

addMessage(Messages.ERR_NO_PERM);
// or
translations.addMessages(Messages.ERR_NO_PLAYER, Messages.ERR_NO_PERM);
translations.

addMessages(Messages.ERR_NO_PLAYER, Messages.ERR_NO_PERM);
// or just:
translations.addMessages(TranslationsFramework.messageFieldsFromClass(Messages.class));
translations.

addMessages(TranslationsFramework.messageFieldsFromClass(Messages.class));
```

### Build Messages from Translations directly

If you use your translations instance to create a message, it will automatically be added
to your translations.

```Java
ERR_NO_PERM = translations.message("error.no_perm");
ERR_NO_PERM = translations.messageBuilder("error.no_perm")
.withDefault("<prefix_negative>No permission!</prefix_negative>")
.build();
ERR_NO_PERM =translations.

message("error.no_perm");

ERR_NO_PERM =translations.

messageBuilder("error.no_perm")
.

withDefault("<prefix_negative>No permission!</prefix_negative>")
.

build();
```

### Message as Component

Messages are implementations of the TranslatableComponent interface, which means that they automatically
render in the player client locale.

```Java

// on Paper server:
player.sendMessage(Messages.ERR_NO_PLAYER);
player.sendMessage(Messages.ERR_NO_PLAYER.insertString("input", args[0]));
player.

sendMessage(Messages.ERR_NO_PLAYER.insertString("input", args[0]));

// on Spigot server:
BukkitTinyTranslations.sendMessage(player, Messages.ERR_NO_PLAYER);
BukkitTinyTranslations.sendMessageIfNotEmpty(player, Messages.ERR_NO_PLAYER);
BukkitTinyTranslations.

sendMessage(player, Messages.ERR_NO_PLAYER);
BukkitTinyTranslations.

sendMessageIfNotEmpty(player, Messages.ERR_NO_PLAYER);
```

### Other Formats

You can also format a Message into any other format with like so:

```Java
Message ERR_NO_PERM = new MessageBuilder("err.no_perm")
.withDefault("<prefix_negative>No permissions!</prefix_negative>")
Expand Down
4 changes: 2 additions & 2 deletions TinyTranslations-bukkit-common/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@
public class BukkitGlobalMessages {


public static final Message FORMAT_PLAYER = new MessageBuilder("format.player")
.withDefault("<hover:show_text:'{player:uuid}'>{player:name}</hover>")
.withPlaceholder("player")
.build();
public static final Message FORMAT_ENTITY = new MessageBuilder("format.entity")
.withDefault("{selector:'@[uuid={entity:uuid}]'}")
.withPlaceholder("entity")
.build();
public static final Message FORMAT_WORLD = new MessageBuilder("format.world")
.withDefault("{world:name}")
.withPlaceholder("world")
.build();
public static final Message FORMAT_BLOCK = new MessageBuilder("format.block")
.withDefault("<hover:show_text:'X: {bloc:x}\nY: {bloc:y}\nZ: {bloc:z}\nWorld: {bloc:world:name}'>{block:type}<{bloc:x};{bloc:y};{bloc:z}></hover>")
.withPlaceholder("block")
.build();
public static final Message FORMAT_LOCATION = new MessageBuilder("format.location")
.withDefault("<hover:show_text:'X: {loc:x}\nY: {loc:y}\nZ: {loc:z}\nYaw: {loc:yaw}\nPitch: {loc:pitch}\nWorld: {loc:world:name}'><{loc:x};{loc:y};{loc:z}></hover>")
.withPlaceholder("loc")
.build();
public static final Message FORMAT_VECTOR = new MessageBuilder("format.vector")
.withDefault("<{vector:x};{vector:y};{vector:z}>")
.withPlaceholder("vector")
.build();
public static final Message FORMAT_ITEM = new MessageBuilder("format.item")
.withDefault("{item:amount}x{item:type}")
.withPlaceholder("item")
.build();
public static final Message FORMAT_PLAYER = new MessageBuilder("format.player")
.withDefault("<hover:show_text:'{player:uuid}'>{player:name}</hover>")
.withPlaceholder("player")
.build();
public static final Message FORMAT_ENTITY = new MessageBuilder("format.entity")
.withDefault("{selector:'@[uuid={entity:uuid}]'}")
.withPlaceholder("entity")
.build();
public static final Message FORMAT_WORLD = new MessageBuilder("format.world")
.withDefault("{world:name}")
.withPlaceholder("world")
.build();
public static final Message FORMAT_BLOCK = new MessageBuilder("format.block")
.withDefault("<hover:show_text:'X: {bloc:x}\nY: {bloc:y}\nZ: {bloc:z}\nWorld: {bloc:world:name}'>{block:type}<{bloc:x};{bloc:y};{bloc:z}></hover>")
.withPlaceholder("block")
.build();
public static final Message FORMAT_LOCATION = new MessageBuilder("format.location")
.withDefault("<hover:show_text:'X: {loc:x}\nY: {loc:y}\nZ: {loc:z}\nYaw: {loc:yaw}\nPitch: {loc:pitch}\nWorld: {loc:world:name}'><{loc:x};{loc:y};{loc:z}></hover>")
.withPlaceholder("loc")
.build();
public static final Message FORMAT_VECTOR = new MessageBuilder("format.vector")
.withDefault("<{vector:x};{vector:y};{vector:z}>")
.withPlaceholder("vector")
.build();
public static final Message FORMAT_ITEM = new MessageBuilder("format.item")
.withDefault("{item:amount}x{item:type}")
.withPlaceholder("item")
.build();
}
Loading

0 comments on commit ad8adf4

Please sign in to comment.