{"id":14985666,"url":"https://github.com/cubbossa/tinytranslations","last_synced_at":"2025-04-11T22:05:59.183Z","repository":{"id":41066624,"uuid":"491590529","full_name":"CubBossa/TinyTranslations","owner":"CubBossa","description":"A translation framework for minecraft servers","archived":false,"fork":false,"pushed_at":"2024-08-14T16:49:45.000Z","size":1652,"stargazers_count":17,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T22:05:50.015Z","etag":null,"topics":["annotation","bukkit","component","kyori","language","minecraft","minimessage","paper","plugin","spigot","translatable","translation","yml"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CubBossa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-12T16:31:11.000Z","updated_at":"2024-09-17T04:43:32.000Z","dependencies_parsed_at":"2023-11-22T22:23:06.535Z","dependency_job_id":"ac46a482-8763-4c30-ad31-a2710870ff3a","html_url":"https://github.com/CubBossa/TinyTranslations","commit_stats":{"total_commits":206,"total_committers":2,"mean_commits":103.0,"dds":0.1310679611650486,"last_synced_commit":"6a39f3d784587a62422e3ca37b676497daa38588"},"previous_names":["cubbossa/tinytranslations"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CubBossa%2FTinyTranslations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CubBossa%2FTinyTranslations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CubBossa%2FTinyTranslations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CubBossa%2FTinyTranslations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CubBossa","download_url":"https://codeload.github.com/CubBossa/TinyTranslations/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248487715,"owners_count":21112191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annotation","bukkit","component","kyori","language","minecraft","minimessage","paper","plugin","spigot","translatable","translation","yml"],"created_at":"2024-09-24T14:11:27.298Z","updated_at":"2025-04-11T22:05:59.156Z","avatar_url":"https://github.com/CubBossa.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TinyTranslations\n\nA translation framework to translate chat messages.\nThis framework builds\nupon [Kyori Components and the MiniMessage format](https://docs.adventure.kyori.net/minimessage/format.html).\n\nJoin the [Discord Server](https://discord.com/invite/wDecCCRXFv) for support\n\n## Wiki\n\n- [Overview](https://github.com/CubBossa/Translations/wiki)\n- [Setup](https://github.com/CubBossa/Translations#maven)\n- Messages\n    - [Overview](https://github.com/CubBossa/Translations/wiki/Messages)\n    - [Using Placeholders](https://github.com/CubBossa/Translations/wiki/Placeholders)\n    - [Send or Use Messages](https://github.com/CubBossa/Translations/wiki/Send-or-Use-Messages)\n    - [Alternative Chat Formats](https://github.com/CubBossa/Translations/wiki/Legacy-Format-Support)\n    - [Per Player Locale](https://github.com/CubBossa/Translations/wiki/Per-Player-Locale)\n- [Styles](https://github.com/CubBossa/Translations/wiki/Styles)\n- Storages\n    - [Creating a Custom Storage](https://github.com/CubBossa/Translations/wiki/Creating-a--Custom-Storage)\n    - [YAML](https://github.com/CubBossa/Translations/wiki/Yaml-Storage)\n    - [Properties](https://github.com/CubBossa/Translations/wiki/Properties-Storage)\n\n## UML Overview\n\n![UML Overview](visualization/uml_overview.svg)\n\n## Maven\n\nInstall the following repository and dependency in your pom.xml or your build.gradle.\nMake sure to use the latest version.\n\n```XML\n\n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003eTranslations\u003c/id\u003e\n        \u003curl\u003ehttps://nexus.leonardbausenwein.de/repository/maven-public/\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n```\n\n```XML\n\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003ede.cubbossa\u003c/groupId\u003e\n        \u003c!-- alternatively TinyTranslations-paper --\u003e\n        \u003cartifactId\u003eTinyTranslations-bukkit\u003c/artifactId\u003e\n        \u003cversion\u003e[version]\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\n### Shading\n\nWhen shading, it is highly recommended to relocate the resource within your plugin.\nThis assures that no other plugin loads outdated Translations classes before your\nplugin can load the latest classes. Occurring errors would potentially disable your plugin on startup.\n\n```XML\n\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        ...\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-shade-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e3.2.4\u003c/version\u003e\n            \u003cexecutions\u003e\n                \u003cexecution\u003e\n                    \u003cphase\u003epackage\u003c/phase\u003e\n                    \u003cgoals\u003e\n                        \u003cgoal\u003eshade\u003c/goal\u003e\n                    \u003c/goals\u003e\n                    \u003cconfiguration\u003e\n                        \u003crelocations\u003e\n                            \u003crelocation\u003e\n                                \u003cpattern\u003ede.cubbossa.tinytranslations\u003c/pattern\u003e\n                                \u003cshadedPattern\u003e[yourpluginpath].libs.tinytranslations\u003c/shadedPattern\u003e\n                            \u003c/relocation\u003e\n                        \u003c/relocations\u003e\n                    \u003c/configuration\u003e\n                \u003c/execution\u003e\n            \u003c/executions\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\nor with gradle:\n\n```groovy\ntasks.shadowJar {\n    minimize()\n    relocate 'de.cubbossa.tinytranslations', '[yourpluginpath].libs.tinytranslations'\n}\n```\n\n### Dependencies (Spigot Libraries)\n\nYour server must find and load the Kyori Adventure classes for Translations to work.\nEither use `TinyTranslations-paper` to use the Adventure classes provided by paper or use\n`TinyTranslations-bukkit` to include them as shaded dependencies.\n\n## Overview\n\nTranslations are split into global server-wide translations and local application translations.\nTranslations exist in a treelike structure.\nThe global root Translations instance allows to provide translation strings and styles for all following Translations\ninstances.\nEach Translation instance can be forked into a child, which then uses all styles and messages of its parent but has some\nencapsulated translations on its own. Mostly, there will be one global Translations instance and one per plugin.\n\nExample of the Server folder structure and how translations are included:\n\n```YML\n/Server\n  /plugins\n\n    /lang\n      global_styles.properties # \u003c--- global styling rules\n      en-US.properties # \u003c--- global messages (like the server name)\n\n    /YourPlugin\n      /lang\n        styles.properties # \u003c--- application only styles\n        en-US.properties # \u003c--- application only messages\n```\n\nStyles are a way to create new tag resolvers\n\n```properties\n# We use opening tags to define simple styles.\ntext_l=\"\u003cwhite\u003e\"\ntext=\"\u003cgray\u003e\"\ntext_d=\"\u003cdark-gray\u003e\"\n# Or slot based styles for more complex patterns\n# The list-el example will render \"\u003clist-el\u003eabc\u003c/list-el\u003e\" as \"- abc\", where the \"-\" is gray and \"abc\" is white.\nlist_el=\"\u003cgray\u003e- \u003c/gray\u003e\u003cwhite\u003e{slot}\u003c/white\u003e\\n\"\n# the url tag renders only a short version but opens the whole url on click.\n#     https://docs.advntr.dev/minimessage/format.html\n# becomes\n#     https://docs.advntr.dev/min...tml\n# The first occurring '/' after the domain separates the tail, which will show its first and last three letters.\nurl=\"\u003cblue\u003e\u003cu\u003e\u003cclick:open_url:\"{slot}\"\u003e\u003chover:show_text:\"Click to open url\"\u003e\u003cshorten_url\u003e{slot}\u003c/shorten_url\u003e\u003c/hover\u003e\u003c/click\u003e\u003c/u\u003e\u003c/blue\u003e\"\n```\n\nMessages can be stored in many ways, like SQL, Yaml or properties.\nIn a properties file, messages would look like this:\n\n```properties\nsome.example.message=\"\u003ctext-light\u003eSome light text \u003caqua\u003ethat can also be styled directly\u003c/aqua\u003e\u003c/text-light\u003e\"\nsome.example.reference=\"An embedded message: {msg:some.example.message}\"\n```\n\nAs you can see in the example, messages can be embedded into each other, which\nallows you to simply create own messages and use them all over the place.\nWhy is this useful? Think of the following example from my plugin:\n\n```properties\n# c-brand is a style for the main plugin color.\n# bg and bg-dark are global styles.\nprefix=\"\u003cprimary\u003ePathFinder \u003c/primary\u003e\u003cbg_dark\u003e| \u003c/bg_dark\u003e\u003cbg\u003e\"\nother.message=\"{msg:prefix}Hello.\"\n```\n\nPrefix is not a message that is enforced by the plugin.\nUsers can simply create the entry, and it will be loaded by the plugin and\nembedded in other messages.\n\n## Setup\n\n```Java\nimport de.cubbossa.tinytranslations.MessageBuilder;\n\nclass Messages {\n    public static final Message PREFIX = new MessageBuilder(\"prefix\")\n            .withDefault(\"\u003cgradient:#ff0000:#ffff00:#ff0000\u003eMy Awesome Plugin\u003c/gradient\u003e\")\n            .build();\n    public static final Message NO_PERM = new MessageBuilder(\"no_perm\")\n            .withDefault(\"\u003cprefix_negative\u003eNo permissions!\u003c/prefix_negative\u003e\")\n            .build();\n}\n\n\nclass ExamplePlugin extends JavaPlugin {\n\n    Translations translations;\n\n    public void onEnable() {\n        // create a Translations instance for your plugin \n        translations = BukkitTinyTranslations.application(this);\n\n        // define the storage types for your plugins locale\n        translations.setMessageStorage(new PropertiesMessageStorage(new File(getDataFolder(), \"/lang/\")));\n        translations.setStyleStorage(new PropertiesStyleStorage(new File(getDataFolder(), \"/lang/styles.properties\")));\n\n        // register all your messages to your Translations instance\n        // a message cannot be translated without a Translations instance, which works as\n        // messageTranslator.\n        translations.addMessages(messageA, messageB, messageC);\n        translations.addMessage(messageD);\n        // just load all public static final messages declared in Messages.class\n        translations.addMessages(TinyTranslations.messageFieldsFromClass(Messages.class));\n\n        // They will not overwrite pre-existing values.\n        // You only need to save values that you assigned programmatically, like from a\n        // message builder. You can also create a de.properties resource and save it as file instead.\n        // Then there is no need to write the german defaults to file here.\n        translations.saveLocale(Locale.ENGLISH);\n        translations.saveLocale(Locale.GERMAN);\n\n        // load all styles and locales from file. This happens for all parent translations,\n        // so all changes to the global styles and translations will apply too.\n        translations.loadStyles();\n        translations.loadLocales();\n    }\n\n    public void onDisable() {\n        // close open Translations instance\n        translations.close();\n    }\n}\n```\n\n### Messages\n\nMessages can easily be set up as statics.\nLocale files can be generated from a class that holds Messages\nas static members.\n\nA fully defined message:\n\n```Java\npublic static final Message ERR_NO_PLAYER = new MessageBuilder(\"error.must_be_player\")\n        .withDefault(\"\u003cprefix_negative\u003eNo player found: '{input}'.\u003c/prefix_negative\u003e\")\n        .withTranslation(Locale.GERMAN, \"\u003cprefix_negative\u003eSpieler nicht gefunden: '{input}'.\u003c/prefix_negative\u003e\")\n        .withComment(\"Used to indicate if no player was found - who would have thought :P\")\n        .withPlaceholder(\"input\", \"The used input that was supposed to be a playername.\")\n        .build();\n```\n\nOr maybe just\n\n```Java\npublic static final Message ERR_NO_PERM = new MessageBuilder(\"error.no_perm\")\n        .withDefault(\"\u003cprefix_negative\u003eNo permission!\u003c/prefix_negative\u003e\")\n        .build();\n```\n\n### Add Messages to Translations\n\nDon't forget to register all messages to your application!!\n\n```Java\ntranslations.addMessage(Messages.ERR_NO_PLAYER);\ntranslations.addMessage(Messages.ERR_NO_PERM);\n// or\ntranslations.addMessages(Messages.ERR_NO_PLAYER, Messages.ERR_NO_PERM);\n// or just:\ntranslations.addMessages(TranslationsFramework.messageFieldsFromClass(Messages.class));\n```\n\n### Build Messages from Translations directly\n\nIf you use your translations instance to create a message, it will automatically be added\nto your translations.\n\n```Java\nERR_NO_PERM = translations.message(\"error.no_perm\");\n\nERR_NO_PERM = translations.messageBuilder(\"error.no_perm\")\n        .withDefault(\"\u003cprefix_negative\u003eNo permission!\u003c/prefix_negative\u003e\")\n        .build();\n```\n\n### Message as Component\n\nMessages are implementations of the TranslatableComponent interface, which means that they automatically\nrender in the player client locale.\n\n```Java\n\n// on Paper server:\nplayer.sendMessage(Messages.ERR_NO_PLAYER);\nplayer.sendMessage(Messages.ERR_NO_PLAYER.insertString(\"input\", args[0]));\n\n// on Spigot server:\nBukkitTinyTranslations.sendMessage(player, Messages.ERR_NO_PLAYER);\nBukkitTinyTranslations.sendMessageIfNotEmpty(player, Messages.ERR_NO_PLAYER);\n```\n\n### Other Formats\n\nYou can also format a Message into any other format with like so:\n\n```Java\nMessage ERR_NO_PERM = new MessageBuilder(\"err.no_perm\")\n        .withDefault(\"\u003cprefix_negative\u003eNo permissions!\u003c/prefix_negative\u003e\")\n        .build();\n\nString s = ERR_NO_PERM.toString(MessageEncoding.LEGACY_PARAGRAPH);\n// -\u003e §cNo permissions!\n\nString s = ERR_NO_PERM.toString(MessageEncoding.LEGACY_AMPERSAND);\n// -\u003e \u0026cNo permissions!\n\nString s = ERR_NO_PERM.toString(MessageEncoding.PLAIN);\n// -\u003e No permissions!\n\nString s = ERR_NO_PERM.toString(MessageEncoding.NBT);\n// -\u003e {\"text\":\"No permissions!\",\"color\":\"red\"}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcubbossa%2Ftinytranslations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcubbossa%2Ftinytranslations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcubbossa%2Ftinytranslations/lists"}