diff --git a/README.md b/README.md index 2281995..16d7831 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ PlayerCoordsAPI provides real-time access to your Minecraft player coordinates t - Lightweight HTTP server running only on localhost providing your coordinates - Client-side only - no server-side components needed - Works in singleplayer and multiplayer -- Mod menu integration allowing you to enable/disable the API and configure CORS +- Mod menu integration allowing you to enable/disable the API, change its port, and configure requests with or without an `Origin` header ## 🚀 Installation @@ -27,27 +27,27 @@ PlayerCoordsAPI provides real-time access to your Minecraft player coordinates t 3. Place the jar in your `.minecraft/mods` folder 4. Launch Minecraft with the Fabric profile -## 🔌 API Usage +## ⚙️ Configuration -| Endpoint | Method | Description | -|---------------|--------|----------------------------------------------------------| -| `/api/coords` | `GET` | Returns the player's current coordinates and world infos | +The API is read-only and only accepts loopback connections such as `127.0.0.1` and `::1`. +From Mod Menu, you can configure: +- Whether the API is enabled +- Which port it listens on (default: `25565`) +- How requests without a CORS `Origin` header are handled +- How requests with a CORS `Origin` header are handled -### Response Format +Requests with an `Origin` can be handled in three different modes: +- `Allow all` +- `Local origins` +- `Whitelist` -```json -{ - "x": 123.45, - "y": 64.00, - "z": -789.12, - "yaw": 180.00, - "pitch": 12.50, - "world": "minecraft:overworld", - "biome": "minecraft:plains", - "uuid": "550e8400-e29b-41d4-a716-446655440000", - "username": "PlayerName" -} -``` +In `Whitelist` mode, you can configure each allowed origin with a scheme, host/IP, and optional port. + +## 🔌 API Usage + +| Endpoint | Method | Description | +|---------------|-----------------|----------------------------------------------------------| +| `/api/coords` | `GET`, `OPTIONS` | Returns the player's current coordinates and world infos | ### Response Fields @@ -71,16 +71,26 @@ PlayerCoordsAPI provides real-time access to your Minecraft player coordinates t | `404` | Player not in world | | `405` | Method not allowed | -## 🔒 Security +### Response Format Example -For security reasons, the API server: -- Only accepts connections from loopback addresses such as `127.0.0.1` and `::1` -- Runs on port `25565` by default -- Provides read-only access to player position data -- Uses a configurable CORS policy. By default it allows all origins for backward compatibility, but you can restrict it in the config screen +```json +{ + "x": 123.45, + "y": 64.00, + "z": -789.12, + "yaw": 180.00, + "pitch": 12.50, + "world": "minecraft:overworld", + "biome": "minecraft:plains", + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "username": "PlayerName" +} +``` ## 🛠️ Examples +Replace `25565` with your configured port if you changed it in the Mod Menu. + ### cURL ```bash curl http://localhost:25565/api/coords diff --git a/build.gradle b/build.gradle index 56f50a6..946e22d 100644 --- a/build.gradle +++ b/build.gradle @@ -11,11 +11,6 @@ base { } repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. maven { name = "Terraformers" url = "https://maven.terraformersmc.com/releases" // Mod Menu repo requires explicit releases path on CI @@ -36,25 +31,16 @@ loom { } dependencies { - // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - // Sun's HTTP server is included in JDK, no additional dependency needed - - // Mod Menu modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" - - // Cloth Config modImplementation "me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}" } processResources { - // Keep fabric.mod.json versions in sync with gradle.properties inputs.property "version", project.version inputs.property "loader_version", project.loader_version inputs.property "minecraft_version", project.minecraft_version @@ -79,9 +65,6 @@ tasks.withType(JavaCompile).configureEach { } java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. withSourcesJar() sourceCompatibility = JavaVersion.VERSION_21 @@ -100,7 +83,6 @@ jar { } } -// configure the maven publication publishing { publications { create("mavenJava", MavenPublication) { @@ -109,11 +91,6 @@ publishing { } } - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { - // Add repositories to publish to here. - // Notice: This block does NOT have the same function as the block in the top level. - // The repositories here will be used for publishing your artifact, not for - // retrieving dependencies. } } diff --git a/gradle.properties b/gradle.properties index 340debc..bb31da1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ loader_version=0.18.4 loom_version=1.15.4 # Mod Properties -mod_version=0.1.5 +mod_version=0.2.0 maven_group=fr.sukikui.playercoordsapi archives_base_name=playercoordsapi diff --git a/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java b/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java index 56e11c2..db3185f 100644 --- a/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java +++ b/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; @@ -24,9 +25,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +/** + * Client-side entrypoint that exposes the local HTTP API and tracks its runtime state. + */ public class PlayerCoordsAPIClient implements ClientModInitializer { - private static final int PORT = 25565; - private static final long START_RETRY_DELAY_MS = 5_000L; private static final String ALLOWED_METHODS = "GET, OPTIONS"; private static final String DEFAULT_ALLOWED_HEADERS = "Content-Type, Authorization"; private static final String ACCESS_DENIED_RESPONSE = "{\"error\": \"Access denied\"}"; @@ -34,17 +36,46 @@ public class PlayerCoordsAPIClient implements ClientModInitializer { private static final String NON_BROWSER_CLIENTS_NOT_ALLOWED_RESPONSE = "{\"error\": \"Non-browser local clients not allowed\"}"; private static final String METHOD_NOT_ALLOWED_RESPONSE = "{\"error\": \"Method not allowed\"}"; private static final String PLAYER_NOT_IN_WORLD_RESPONSE = "{\"error\": \"Player not in world\"}"; + private static volatile ServerStatus serverStatus = ServerStatus.disabled(ModConfig.DEFAULT_API_PORT); + private static PlayerCoordsAPIClient instance; private HttpServer server; private ExecutorService serverExecutor; private boolean serverStarted = false; private boolean lastConfigEnabled; - private long nextStartAttemptAt = 0L; + private int lastConfiguredPort; + private boolean startBlockedUntilRefresh; + private boolean retryRequested; private volatile PlayerSnapshot latestSnapshot; + /** + * Returns the current server status for the config screen. + */ + public static ServerStatus getServerStatus() { + return serverStatus; + } + + /** + * Requests a one-shot reevaluation of the current server configuration. + */ + public static void requestServerRefresh() { + if (instance != null) { + instance.retryRequested = true; + } + } + + /** + * Registers lifecycle hooks and starts the API immediately when enabled. + */ @Override public void onInitializeClient() { - lastConfigEnabled = PlayerCoordsAPI.getConfig().enabled; + instance = this; + ModConfig config = PlayerCoordsAPI.getConfig(); + lastConfigEnabled = config.enabled; + lastConfiguredPort = ModConfig.normalizeApiPort(config.apiPort); + serverStatus = lastConfigEnabled + ? ServerStatus.stopped(lastConfiguredPort) + : ServerStatus.disabled(lastConfiguredPort); if (lastConfigEnabled) { tryStartServer(); @@ -52,7 +83,7 @@ public void onInitializeClient() { ClientTickEvents.END_CLIENT_TICK.register(client -> { updateSnapshot(client); - handleConfigState(PlayerCoordsAPI.getConfig().enabled); + handleConfigState(PlayerCoordsAPI.getConfig()); }); ClientWorldEvents.AFTER_CLIENT_WORLD_CHANGE.register((client, world) -> updateSnapshot(client)); @@ -65,26 +96,62 @@ public void onInitializeClient() { PlayerCoordsAPI.LOGGER.info("Registered config monitor"); } - private void handleConfigState(boolean configEnabled) { + /** + * Applies runtime changes when the user edits the config in-game. + */ + private void handleConfigState(ModConfig config) { + boolean configEnabled = config.enabled; + int configuredPort = ModConfig.normalizeApiPort(config.apiPort); + + if (configuredPort != lastConfiguredPort) { + lastConfiguredPort = configuredPort; + lastConfigEnabled = configEnabled; + clearStartBlock(); + + if (serverStarted) { + stopServer(); + } + + if (configEnabled) { + tryStartServer(); + } + + return; + } + if (configEnabled != lastConfigEnabled) { lastConfigEnabled = configEnabled; + clearStartBlock(); if (configEnabled) { - nextStartAttemptAt = 0L; tryStartServer(); } else { - nextStartAttemptAt = 0L; stopServer(); } return; } - if (configEnabled && !serverStarted) { + if (retryRequested) { + retryRequested = false; + + if (!configEnabled) { + return; + } + + clearStartBlock(); + tryStartServer(); + return; + } + + if (configEnabled && !serverStarted && !startBlockedUntilRefresh) { tryStartServer(); } } + /** + * Refreshes the cached player snapshot from the client thread. + */ private void updateSnapshot(MinecraftClient client) { PlayerEntity player = client.player; ClientWorld worldObj = client.world; @@ -112,52 +179,65 @@ private void updateSnapshot(MinecraftClient client) { ); } + /** + * Clears the cached player snapshot when no valid in-game state is available. + */ private void clearSnapshot() { latestSnapshot = null; } + /** + * Attempts to start the embedded HTTP server with the current config. + */ private void tryStartServer() { - if (serverStarted) { + if (serverStarted || startBlockedUntilRefresh) { return; } - long now = System.currentTimeMillis(); - - if (now < nextStartAttemptAt) { - return; - } + int port = lastConfiguredPort; try { - PlayerCoordsAPI.LOGGER.info("Starting PlayerCoordsAPI HTTP server on port " + PORT); - server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), PORT), 0); + PlayerCoordsAPI.LOGGER.info("Starting PlayerCoordsAPI HTTP server on port " + port); + server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 0); server.createContext("/api/coords", this::handleCoordsRequest); serverExecutor = Executors.newSingleThreadExecutor(); server.setExecutor(serverExecutor); server.start(); serverStarted = true; - nextStartAttemptAt = 0L; + clearStartBlock(); + serverStatus = ServerStatus.running(port); PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server started successfully"); } catch (IOException e) { cleanupServerResources(); - nextStartAttemptAt = now + START_RETRY_DELAY_MS; - PlayerCoordsAPI.LOGGER.warn( - "Failed to start PlayerCoordsAPI HTTP server, retrying in {} seconds", - START_RETRY_DELAY_MS / 1000, - e - ); + startBlockedUntilRefresh = true; + String failureDetail = formatStartFailure(port, e); + serverStatus = ServerStatus.failed(port, failureDetail); + PlayerCoordsAPI.LOGGER.warn("Failed to start PlayerCoordsAPI HTTP server on port {}: {}", port, failureDetail); } } + /** + * Stops the embedded HTTP server and updates the exposed runtime status. + */ private void stopServer() { if (server == null && serverExecutor == null) { + serverStatus = ServerStatus.disabled(lastConfiguredPort); + clearStartBlock(); return; } PlayerCoordsAPI.LOGGER.info("Stopping PlayerCoordsAPI HTTP server"); cleanupServerResources(); + clearStartBlock(); + serverStatus = lastConfigEnabled + ? ServerStatus.stopped(lastConfiguredPort) + : ServerStatus.disabled(lastConfiguredPort); PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server stopped successfully"); } + /** + * Releases all server-side resources after stop or failed startup. + */ private void cleanupServerResources() { if (server != null) { server.stop(0); @@ -172,6 +252,39 @@ private void cleanupServerResources() { serverStarted = false; } + /** + * Clears the startup failure gate so the next refresh can retry startup. + */ + private void clearStartBlock() { + startBlockedUntilRefresh = false; + } + + /** + * Maps low-level bind failures to short user-facing status details. + */ + private static String formatStartFailure(int port, IOException exception) { + String message = exception.getMessage(); + + if (exception instanceof BindException && message != null) { + if (message.equalsIgnoreCase("Address already in use")) { + return "Port already in use"; + } + + if (message.equalsIgnoreCase("Permission denied")) { + return "Permission denied"; + } + } + + if (message == null || message.isBlank()) { + return exception.getClass().getSimpleName(); + } + + return message; + } + + /** + * Handles all requests to the local coordinates endpoint. + */ private void handleCoordsRequest(HttpExchange exchange) throws IOException { InetAddress remoteAddress = exchange.getRemoteAddress().getAddress(); if (remoteAddress == null || !remoteAddress.isLoopbackAddress()) { @@ -206,6 +319,9 @@ private void handleCoordsRequest(HttpExchange exchange) throws IOException { } } + /** + * Evaluates whether the request should receive CORS headers or be rejected. + */ private CorsDecision evaluateCorsDecision(HttpExchange exchange) { ModConfig config = PlayerCoordsAPI.getConfig(); String requestOrigin = exchange.getRequestHeaders().getFirst("Origin"); @@ -229,6 +345,9 @@ private CorsDecision evaluateCorsDecision(HttpExchange exchange) { .orElseGet(() -> CorsDecision.denied(ORIGIN_NOT_ALLOWED_RESPONSE)); } + /** + * Mirrors requested CORS headers back to the client when present. + */ private String resolveAllowedHeaders(HttpExchange exchange) { String requestedHeaders = exchange.getRequestHeaders().getFirst("Access-Control-Request-Headers"); @@ -239,6 +358,9 @@ private String resolveAllowedHeaders(HttpExchange exchange) { return requestedHeaders; } + /** + * Sends an HTTP response and attaches CORS headers when required. + */ private void sendResponse(HttpExchange exchange, int statusCode, String response, CorsDecision corsDecision) throws IOException { if (corsDecision.allowOrigin() != null) { exchange.getResponseHeaders().set("Access-Control-Allow-Origin", corsDecision.allowOrigin()); @@ -262,6 +384,9 @@ private void sendResponse(HttpExchange exchange, int statusCode, String response } } + /** + * Escapes JSON string content for the lightweight manual serializer used by the API. + */ private static String escapeJson(String value) { StringBuilder escaped = new StringBuilder(value.length() + 16); @@ -289,20 +414,35 @@ private static String escapeJson(String value) { return escaped.toString(); } + /** + * Describes how a single request should be handled from a CORS perspective. + */ private record CorsDecision(boolean allowed, String allowOrigin, String allowHeaders, boolean varyByOrigin, String errorResponse) { + /** + * Creates a decision for an allowed request that should expose CORS headers. + */ private static CorsDecision allowed(String allowOrigin, String allowHeaders, boolean varyByOrigin) { return new CorsDecision(true, allowOrigin, allowHeaders, varyByOrigin, null); } + /** + * Creates a denied request decision with a JSON error payload. + */ private static CorsDecision denied(String errorResponse) { return new CorsDecision(false, null, null, false, errorResponse); } + /** + * Creates an allowed request decision that does not emit CORS headers. + */ private static CorsDecision noCors() { return new CorsDecision(true, null, null, false, null); } } + /** + * Immutable snapshot of the current player state served by the HTTP endpoint. + */ private record PlayerSnapshot( double x, double y, @@ -314,6 +454,9 @@ private record PlayerSnapshot( String uuid, String username ) { + /** + * Serializes the snapshot to the JSON payload returned by the API. + */ private String toJson() { return String.format(Locale.US, "{\"x\": %.2f, \"y\": %.2f, \"z\": %.2f, \"yaw\": %.2f, \"pitch\": %.2f, \"world\": \"%s\", \"biome\": \"%s\", \"uuid\": \"%s\", \"username\": \"%s\"}", @@ -329,4 +472,47 @@ private String toJson() { ); } } + + /** + * Exposes the server state consumed by the config screen banner. + */ + public record ServerStatus(State state, int port, String detail) { + /** + * High-level lifecycle states for the embedded API server. + */ + public enum State { + DISABLED, + STOPPED, + RUNNING, + FAILED + } + + /** + * Creates a disabled status for the given configured port. + */ + private static ServerStatus disabled(int port) { + return new ServerStatus(State.DISABLED, port, null); + } + + /** + * Creates a stopped status for the given configured port. + */ + private static ServerStatus stopped(int port) { + return new ServerStatus(State.STOPPED, port, null); + } + + /** + * Creates a running status for the given configured port. + */ + private static ServerStatus running(int port) { + return new ServerStatus(State.RUNNING, port, null); + } + + /** + * Creates a failed status with a short user-facing detail string. + */ + private static ServerStatus failed(int port, String detail) { + return new ServerStatus(State.FAILED, port, detail); + } + } } diff --git a/src/client/java/fr/sukikui/playercoordsapi/config/ModMenuIntegration.java b/src/client/java/fr/sukikui/playercoordsapi/config/ModMenuIntegration.java index 5f85109..1afeccf 100644 --- a/src/client/java/fr/sukikui/playercoordsapi/config/ModMenuIntegration.java +++ b/src/client/java/fr/sukikui/playercoordsapi/config/ModMenuIntegration.java @@ -5,8 +5,14 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +/** + * Registers the custom config screen with Mod Menu. + */ @Environment(EnvType.CLIENT) public class ModMenuIntegration implements ModMenuApi { + /** + * Returns the screen factory used by Mod Menu for this mod. + */ @Override public ConfigScreenFactory getModConfigScreenFactory() { return PlayerCoordsConfigScreen::new; diff --git a/src/client/java/fr/sukikui/playercoordsapi/config/PlayerCoordsConfigScreen.java b/src/client/java/fr/sukikui/playercoordsapi/config/PlayerCoordsConfigScreen.java index 98ac3e5..89decd4 100644 --- a/src/client/java/fr/sukikui/playercoordsapi/config/PlayerCoordsConfigScreen.java +++ b/src/client/java/fr/sukikui/playercoordsapi/config/PlayerCoordsConfigScreen.java @@ -1,5 +1,6 @@ package fr.sukikui.playercoordsapi.config; +import fr.sukikui.playercoordsapi.PlayerCoordsAPIClient; import me.shedaniel.autoconfig.AutoConfig; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -20,10 +21,15 @@ import org.joml.Vector2i; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.Set; +/** + * Custom Mod Menu screen used to edit the local API settings and origin whitelist. + */ @Environment(EnvType.CLIENT) final class PlayerCoordsConfigScreen extends Screen { private static final TooltipPositioner TOP_TOOLTIP_POSITIONER = (screenWidth, screenHeight, x, y, width, height) -> @@ -34,8 +40,13 @@ final class PlayerCoordsConfigScreen extends Screen { private static final int CONTENT_WIDTH = 340; private static final int ROW_HEIGHT = 20; private static final int ROW_SPACING = 28; + private static final int ROW_GAP = 6; private static final int BUTTON_HEIGHT = 20; - private static final int CONTROL_WIDTH = 170; + private static final int STATUS_BOX_Y = 28; + private static final int ORIGINS_SECTION_TOP_GAP = 12; + private static final int STATUS_BOX_HEIGHT = 16; + private static final int RESET_BUTTON_WIDTH = 46; + private static final int CONTROL_WIDTH = 144; private static final int ORIGIN_ROW_HEIGHT = 28; private static final int ORIGIN_ROW_SPACING = 6; private static final int ORIGIN_SCHEME_WIDTH = 64; @@ -48,45 +59,60 @@ final class PlayerCoordsConfigScreen extends Screen { private final List originRows = new ArrayList<>(); private TextWidget enabledLabel; + private TextWidget apiPortLabel; private TextWidget corsPolicyLabel; private TextWidget nonBrowserClientsLabel; private CyclingButtonWidget enabledButton; + private TextFieldWidget apiPortField; private CyclingButtonWidget corsPolicyButton; private CyclingButtonWidget nonBrowserClientsButton; + private ButtonWidget apiPortResetButton; + private ButtonWidget corsPolicyResetButton; + private ButtonWidget nonBrowserClientsResetButton; private ButtonWidget addOriginButton; + private ButtonWidget applyButton; private ButtonWidget doneButton; - private Optional whitelistError = Optional.empty(); + private boolean hasValidationError; + private String apiPortValue; private int scrollOffset; private int listTop; private int listBottom; private int listLeft; private int listWidth; - private int corsPolicyRowY; - private int nonBrowserClientsRowY; + /** + * Creates the screen and copies the persisted config into a mutable working draft. + */ PlayerCoordsConfigScreen(Screen parent) { super(Text.translatable("text.autoconfig.playercoordsapi.title")); this.parent = parent; ModConfig config = AutoConfig.getConfigHolder(ModConfig.class).getConfig(); workingConfig.enabled = config.enabled; + workingConfig.apiPort = ModConfig.normalizeApiPort(config.apiPort); workingConfig.corsPolicy = config.corsPolicy == null ? ModConfig.CorsPolicy.ALLOW_ALL : config.corsPolicy; workingConfig.allowNonBrowserLocalClients = config.allowNonBrowserLocalClients; workingConfig.allowedOrigins = new ArrayList<>(config.allowedOrigins == null ? List.of() : config.allowedOrigins); workingConfig.originEntries = new ArrayList<>(config.originEntries == null ? List.of() : config.originEntries); + apiPortValue = Integer.toString(workingConfig.apiPort); this.originDrafts = createDrafts(workingConfig.originEntries, workingConfig.allowedOrigins); syncOriginDraftsWithCorsPolicy(); } + /** + * Builds all visible widgets for the current screen size and working config. + */ @Override protected void init() { int left = this.width / 2 - CONTENT_WIDTH / 2; int controlX = left + CONTENT_WIDTH - CONTROL_WIDTH; - int y = 32; + int resetX = controlX - ROW_GAP - RESET_BUTTON_WIDTH; + int labelWidth = resetX - ROW_GAP - left; + int y = 52; - enabledLabel = this.addDrawableChild(new TextWidget(left, y + 6, CONTROL_WIDTH - 12, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.enabled"), this.textRenderer)); + enabledLabel = this.addDrawableChild(new TextWidget(left, y + 6, labelWidth, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.enabled"), this.textRenderer)); enabledLabel.active = false; enabledButton = this.addDrawableChild(CyclingButtonWidget.onOffBuilder( ScreenTexts.ON.copy().formatted(Formatting.GREEN), @@ -97,32 +123,38 @@ protected void init() { .build(controlX, y, CONTROL_WIDTH, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.enabled"), (button, value) -> workingConfig.enabled = value)); y += ROW_SPACING; - - corsPolicyRowY = y; - corsPolicyLabel = this.addDrawableChild(new TextWidget(left, y + 6, CONTROL_WIDTH - 12, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.cors_policy"), this.textRenderer)); - corsPolicyLabel.active = false; - corsPolicyButton = this.addDrawableChild(CyclingButtonWidget.builder(PlayerCoordsConfigScreen::getCorsPolicyLabel, workingConfig.corsPolicy) - .values(ModConfig.CorsPolicy.values()) - .omitKeyText() - .build(controlX, y, CONTROL_WIDTH, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.cors_policy"), (button, value) -> { - workingConfig.corsPolicy = value; - syncOriginDraftsWithCorsPolicy(); - scrollOffset = 0; - clearAndInit(); - })); + apiPortLabel = this.addDrawableChild(new TextWidget(left, y + 6, labelWidth, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.api_port"), this.textRenderer)); + apiPortLabel.active = false; + apiPortResetButton = this.addDrawableChild(ButtonWidget.builder(Text.translatable("config.playercoordsapi.button.reset"), button -> resetApiPort()) + .dimensions(resetX, y, RESET_BUTTON_WIDTH, ROW_HEIGHT) + .build()); + apiPortField = this.addDrawableChild(new TextFieldWidget(this.textRenderer, controlX, y, CONTROL_WIDTH, ROW_HEIGHT, ScreenTexts.EMPTY)); + apiPortField.setMaxLength(5); + apiPortField.setTextPredicate(value -> value.isEmpty() || value.chars().allMatch(Character::isDigit)); + apiPortField.setPlaceholder(Text.literal(Integer.toString(ModConfig.DEFAULT_API_PORT))); + apiPortField.setText(apiPortValue); + apiPortField.setEditableColor(0xFFE0E0E0); + apiPortField.setUneditableColor(0xFF808080); + apiPortField.setChangedListener(value -> { + apiPortValue = value; + ModConfig.parseApiPort(value).ifPresent(port -> workingConfig.apiPort = port); + updateValidation(); + }); y += ROW_SPACING; - nonBrowserClientsRowY = y; nonBrowserClientsLabel = this.addDrawableChild(new TextWidget( left, y + 6, - CONTROL_WIDTH - 12, + labelWidth, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.allow_non_browser_local_clients"), this.textRenderer )); nonBrowserClientsLabel.active = false; + nonBrowserClientsResetButton = this.addDrawableChild(ButtonWidget.builder(Text.translatable("config.playercoordsapi.button.reset"), button -> resetNonBrowserClients()) + .dimensions(resetX, y, RESET_BUTTON_WIDTH, ROW_HEIGHT) + .build()); nonBrowserClientsButton = this.addDrawableChild(CyclingButtonWidget.onOffBuilder( ScreenTexts.ON.copy().formatted(Formatting.GREEN), ScreenTexts.OFF.copy().formatted(Formatting.RED), @@ -135,16 +167,35 @@ protected void init() { CONTROL_WIDTH, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.allow_non_browser_local_clients"), - (button, value) -> workingConfig.allowNonBrowserLocalClients = value + (button, value) -> { + workingConfig.allowNonBrowserLocalClients = value; + updateValidation(); + } )); + y += ROW_SPACING; + corsPolicyLabel = this.addDrawableChild(new TextWidget(left, y + 6, labelWidth, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.cors_policy"), this.textRenderer)); + corsPolicyLabel.active = false; + corsPolicyResetButton = this.addDrawableChild(ButtonWidget.builder(Text.translatable("config.playercoordsapi.button.reset"), button -> resetCorsPolicy()) + .dimensions(resetX, y, RESET_BUTTON_WIDTH, ROW_HEIGHT) + .build()); + corsPolicyButton = this.addDrawableChild(CyclingButtonWidget.builder(PlayerCoordsConfigScreen::getCorsPolicyLabel, workingConfig.corsPolicy) + .values(ModConfig.CorsPolicy.values()) + .omitKeyText() + .build(controlX, y, CONTROL_WIDTH, ROW_HEIGHT, Text.translatable("config.playercoordsapi.option.cors_policy"), (button, value) -> { + workingConfig.corsPolicy = value; + syncOriginDraftsWithCorsPolicy(); + scrollOffset = 0; + clearAndInit(); + })); + y += ROW_SPACING; int bottomButtonsY = this.height - 28; int addButtonY = bottomButtonsY - BUTTON_HEIGHT - 8; listLeft = left; listWidth = CONTENT_WIDTH; - listTop = y + 40; + listTop = y + ORIGINS_SECTION_TOP_GAP; listBottom = addButtonY - 8; buildOriginRows(); @@ -163,18 +214,30 @@ protected void init() { .dimensions(left, addButtonY, CONTENT_WIDTH, BUTTON_HEIGHT) .build()); + int bottomButtonWidth = (CONTENT_WIDTH - ROW_GAP * 2) / 3; this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> close()) - .dimensions(left, bottomButtonsY, 150, BUTTON_HEIGHT) + .dimensions(left, bottomButtonsY, bottomButtonWidth, BUTTON_HEIGHT) + .build()); + + applyButton = this.addDrawableChild(ButtonWidget.builder(Text.translatable("config.playercoordsapi.button.apply"), button -> { + if (applyChanges()) { + clearAndInit(); + } + }) + .dimensions(left + bottomButtonWidth + ROW_GAP, bottomButtonsY, bottomButtonWidth, BUTTON_HEIGHT) .build()); doneButton = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, button -> saveAndClose()) - .dimensions(left + CONTENT_WIDTH - 150, bottomButtonsY, 150, BUTTON_HEIGHT) + .dimensions(left + (bottomButtonWidth + ROW_GAP) * 2, bottomButtonsY, bottomButtonWidth, BUTTON_HEIGHT) .build()); updateValidation(); clampScroll(); } + /** + * Returns to the parent screen without applying pending changes. + */ @Override public void close() { if (this.client != null) { @@ -182,6 +245,9 @@ public void close() { } } + /** + * Scrolls the whitelist area when the cursor is inside its viewport. + */ @Override public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { if (mouseX >= listLeft @@ -197,8 +263,18 @@ && getMaxScroll() > 0) { return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); } + /** + * Routes clicks to focused text fields before falling back to default widget handling. + */ @Override public boolean mouseClicked(Click click, boolean doubleClick) { + if (apiPortField.isMouseOver(click.x(), click.y()) && apiPortField.mouseClicked(click, doubleClick)) { + apiPortField.setFocused(true); + clearOriginFieldFocus(); + this.setFocused(apiPortField); + return true; + } + if (isWhitelistEditable() && click.button() == 0) { for (OriginRow row : originRows) { if (row.tryFocusField(click, doubleClick)) { @@ -210,6 +286,9 @@ public boolean mouseClicked(Click click, boolean doubleClick) { return super.mouseClicked(click, doubleClick); } + /** + * Forwards key presses to the currently focused text field when applicable. + */ @Override public boolean keyPressed(KeyInput keyInput) { TextFieldWidget focusedTextField = getFocusedTextField(); @@ -221,6 +300,9 @@ public boolean keyPressed(KeyInput keyInput) { return super.keyPressed(keyInput); } + /** + * Forwards typed characters to the currently focused text field when applicable. + */ @Override public boolean charTyped(CharInput charInput) { TextFieldWidget focusedTextField = getFocusedTextField(); @@ -232,6 +314,9 @@ public boolean charTyped(CharInput charInput) { return super.charTyped(charInput); } + /** + * Renders the custom whitelist panel, standard widgets, and active tooltips. + */ @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { layoutOriginRows(); @@ -241,91 +326,154 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { int left = this.width / 2 - CONTENT_WIDTH / 2; int controlX = left + CONTENT_WIDTH - CONTROL_WIDTH; - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 12, 0xFFFFFF); - int headerY = listTop - 20; - int schemeX = left; - int hostX = schemeX + ORIGIN_SCHEME_WIDTH + 8; - int portX = left + CONTENT_WIDTH - ORIGIN_REMOVE_WIDTH - 8 - ORIGIN_PORT_WIDTH; - boolean whitelistEditable = isWhitelistEditable(); - int headerColor = whitelistEditable ? 0xA0A0A0 : 0x707070; - - context.drawTextWithShadow(this.textRenderer, Text.translatable("config.playercoordsapi.option.allowed_origins"), left, headerY - 12, headerColor); - context.drawTextWithShadow(this.textRenderer, Text.translatable("config.playercoordsapi.option.origin_scheme"), schemeX, headerY, headerColor); - context.drawTextWithShadow(this.textRenderer, Text.translatable("config.playercoordsapi.option.origin_host"), hostX, headerY, headerColor); - context.drawTextWithShadow(this.textRenderer, Text.translatable("config.playercoordsapi.option.origin_port"), portX, headerY, headerColor); - + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 12, 0xFFFFFFFF); + renderServerStatus(context, left); if (getMaxScroll() > 0) { int indicatorX = controlX + CONTROL_WIDTH - 8; - context.drawTextWithShadow(this.textRenderer, Text.literal(scrollOffset > 0 ? "^" : ""), indicatorX, headerY - 12, 0x808080); - context.drawTextWithShadow(this.textRenderer, Text.literal(scrollOffset < getMaxScroll() ? "v" : ""), indicatorX, listBottom - 8, 0x808080); + context.drawTextWithShadow(this.textRenderer, Text.literal(scrollOffset > 0 ? "^" : ""), indicatorX, listTop - 12, 0xFF808080); + context.drawTextWithShadow(this.textRenderer, Text.literal(scrollOffset < getMaxScroll() ? "v" : ""), indicatorX, listBottom - 8, 0xFF808080); } - whitelistError.ifPresent(error -> context.drawCenteredTextWithShadow( - this.textRenderer, - error, - this.width / 2, - this.height - 44, - 0xFF5555 - )); - renderHoveredTooltip(context, mouseX, mouseY); } + /** + * Applies changes and closes the screen when validation succeeds. + */ private void saveAndClose() { + if (applyChanges()) { + close(); + } + } + + /** + * Persists the working draft and requests a runtime server refresh. + */ + private boolean applyChanges() { + if (hasValidationError) { + return false; + } + + workingConfig.apiPort = ModConfig.parseApiPort(apiPortValue).orElse(workingConfig.apiPort); workingConfig.originEntries = collectOriginEntries(); workingConfig.allowedOrigins = CorsUtils.normalizeConfiguredOriginsFromEntries(workingConfig.originEntries); ModConfig config = AutoConfig.getConfigHolder(ModConfig.class).getConfig(); config.enabled = workingConfig.enabled; + config.apiPort = ModConfig.normalizeApiPort(workingConfig.apiPort); config.corsPolicy = workingConfig.corsPolicy; config.allowNonBrowserLocalClients = workingConfig.allowNonBrowserLocalClients; config.originEntries = new ArrayList<>(workingConfig.originEntries); config.allowedOrigins = new ArrayList<>(workingConfig.allowedOrigins); AutoConfig.getConfigHolder(ModConfig.class).save(); + PlayerCoordsAPIClient.requestServerRefresh(); + return true; + } - close(); + /** + * Restores the API port field to its default value. + */ + private void resetApiPort() { + workingConfig.apiPort = ModConfig.DEFAULT_API_PORT; + apiPortValue = Integer.toString(ModConfig.DEFAULT_API_PORT); + clearAndInit(); + } + + /** + * Restores the with-origin policy to its default mode. + */ + private void resetCorsPolicy() { + workingConfig.corsPolicy = ModConfig.CorsPolicy.ALLOW_ALL; + syncOriginDraftsWithCorsPolicy(); + scrollOffset = 0; + clearAndInit(); + } + + /** + * Restores the without-origin toggle to its default enabled state. + */ + private void resetNonBrowserClients() { + workingConfig.allowNonBrowserLocalClients = true; + clearAndInit(); } + /** + * Recomputes validation and updates the enabled state of dependent controls. + */ private void updateValidation() { - whitelistError = validateWhitelist(); + hasValidationError = !isConfigValid(); if (addOriginButton != null) { addOriginButton.active = isWhitelistEditable() && !hasEmptyOriginDraft(); } + if (apiPortResetButton != null) { + apiPortResetButton.active = !apiPortValue.equals(Integer.toString(ModConfig.DEFAULT_API_PORT)); + } + + if (corsPolicyResetButton != null) { + corsPolicyResetButton.active = workingConfig.corsPolicy != ModConfig.CorsPolicy.ALLOW_ALL; + } + + if (nonBrowserClientsResetButton != null) { + nonBrowserClientsResetButton.active = !workingConfig.allowNonBrowserLocalClients; + } + + if (applyButton != null) { + applyButton.active = !hasValidationError; + } + if (doneButton != null) { - doneButton.active = whitelistError.isEmpty(); + doneButton.active = !hasValidationError; } } - private Optional validateWhitelist() { + /** + * Returns whether the current working config can be applied safely. + */ + private boolean isConfigValid() { + return isApiPortValid() && isWhitelistValid(); + } + + /** + * Validates the API port input field. + */ + private boolean isApiPortValid() { + return ModConfig.parseApiPort(apiPortValue).isPresent(); + } + + /** + * Validates whitelist rows when whitelist mode is active. + */ + private boolean isWhitelistValid() { if (workingConfig.corsPolicy != ModConfig.CorsPolicy.CUSTOM_WHITELIST) { - return Optional.empty(); + return true; } if (originDrafts.isEmpty()) { - return Optional.of(Text.translatable("config.playercoordsapi.option.allowed_origins.error.empty")); + return false; } - List normalizedOrigins = new ArrayList<>(); + Set normalizedOrigins = new LinkedHashSet<>(); for (OriginDraft draft : originDrafts) { Optional normalizedOrigin = draft.toNormalizedOrigin(); if (normalizedOrigin.isEmpty()) { - return Optional.of(Text.translatable("config.playercoordsapi.option.allowed_origins.error.invalid")); + return false; } - normalizedOrigins.add(normalizedOrigin.get()); - } - - if (CorsUtils.hasDuplicateOrigins(normalizedOrigins)) { - return Optional.of(Text.translatable("config.playercoordsapi.option.allowed_origins.error.duplicate")); + if (!normalizedOrigins.add(normalizedOrigin.get())) { + return false; + } } - return Optional.empty(); + return true; } + /** + * Rebuilds the row widgets used to display editable whitelist entries. + */ private void buildOriginRows() { originRows.clear(); @@ -339,6 +487,9 @@ private void buildOriginRows() { } } + /** + * Positions whitelist row widgets according to scroll state and edit mode. + */ private void layoutOriginRows() { clampScroll(); boolean whitelistEditable = isWhitelistEditable(); @@ -366,6 +517,9 @@ private void layoutOriginRows() { } } + /** + * Draws the bordered background blocks that visually group whitelist rows. + */ private void renderOriginBlocks(DrawContext context) { int panelTop = listTop - 4; int panelBottom = listBottom + 2; @@ -390,6 +544,9 @@ private void renderOriginBlocks(DrawContext context) { } } + /** + * Draws a simple rectangular border using filled quads. + */ private static void drawBorder(DrawContext context, int x, int y, int width, int height, int color) { context.fill(x, y, x + width, y + 1, color); context.fill(x, y + height - 1, x + width, y + height, color); @@ -397,6 +554,9 @@ private static void drawBorder(DrawContext context, int x, int y, int width, int context.fill(x + width - 1, y, x + width, y + height, color); } + /** + * Converts all non-empty UI drafts into persisted origin entries. + */ private List collectOriginEntries() { List originEntries = new ArrayList<>(); @@ -411,21 +571,43 @@ private List collectOriginEntries() { return originEntries; } + /** + * Computes the maximum vertical scroll offset for the whitelist panel. + */ private int getMaxScroll() { int contentHeight = originRows.size() * (ORIGIN_ROW_HEIGHT + ORIGIN_ROW_SPACING) - ORIGIN_ROW_SPACING; int visibleHeight = Math.max(0, listBottom - listTop); return Math.max(0, contentHeight - visibleHeight); } + /** + * Clamps the current whitelist scroll offset to the valid range. + */ private void clampScroll() { scrollOffset = MathHelper.clamp(scrollOffset, 0, getMaxScroll()); } + /** + * Removes one draft row and rebuilds the screen layout. + */ private void removeDraft(OriginDraft draft) { originDrafts.remove(draft); clearAndInit(); } + /** + * Clears focus from all whitelist text fields. + */ + private void clearOriginFieldFocus() { + for (OriginRow row : originRows) { + row.hostField.setFocused(false); + row.portField.setFocused(false); + } + } + + /** + * Keeps an empty placeholder row only while whitelist mode is active. + */ private void syncOriginDraftsWithCorsPolicy() { if (workingConfig.corsPolicy == ModConfig.CorsPolicy.CUSTOM_WHITELIST) { if (originDrafts.isEmpty()) { @@ -438,6 +620,9 @@ private void syncOriginDraftsWithCorsPolicy() { originDrafts.removeIf(OriginDraft::isEmpty); } + /** + * Returns whether the whitelist already contains an empty placeholder row. + */ private boolean hasEmptyOriginDraft() { for (OriginDraft draft : originDrafts) { if (draft.host == null || draft.host.trim().isEmpty()) { @@ -448,6 +633,9 @@ private boolean hasEmptyOriginDraft() { return false; } + /** + * Renders the tooltip currently targeted by the mouse cursor. + */ private void renderHoveredTooltip(DrawContext context, int mouseX, int mouseY) { List tooltip = getHoveredTooltip(mouseX, mouseY); @@ -460,26 +648,20 @@ private void renderHoveredTooltip(DrawContext context, int mouseX, int mouseY) { } } + /** + * Resolves which tooltip should be shown for the current mouse position. + */ private List getHoveredTooltip(int mouseX, int mouseY) { - int headerY = listTop - 20; - int hostX = listLeft + ORIGIN_SCHEME_WIDTH + 8; - int hostWidth = CONTENT_WIDTH - ORIGIN_SCHEME_WIDTH - ORIGIN_PORT_WIDTH - ORIGIN_REMOVE_WIDTH - 24; - int portX = listLeft + CONTENT_WIDTH - ORIGIN_REMOVE_WIDTH - 8 - ORIGIN_PORT_WIDTH; + if (isHoveringLabel(apiPortLabel, mouseX, mouseY)) { + return buildApiPortTooltip(); + } if (isHoveringLabel(corsPolicyLabel, mouseX, mouseY)) { - return buildTooltipLines("config.playercoordsapi.option.cors_policy.tooltip", 6, false); + return buildWithOriginTooltip(); } if (isHoveringLabel(nonBrowserClientsLabel, mouseX, mouseY)) { - return buildTooltipLines("config.playercoordsapi.option.allow_non_browser_local_clients.tooltip", 4, false); - } - - if (isWithin(mouseX, mouseY, hostX, headerY, hostWidth, ROW_HEIGHT)) { - return buildHostTooltip(); - } - - if (isWithin(mouseX, mouseY, portX, headerY, ORIGIN_PORT_WIDTH, ROW_HEIGHT)) { - return buildTooltip("config.playercoordsapi.option.origin_port.tooltip", true); + return buildWithoutOriginTooltip(); } for (OriginRow row : originRows) { @@ -488,14 +670,21 @@ private List getHoveredTooltip(int mouseX, int mouseY) { } if (row.portField.visible && row.portField.isMouseOver(mouseX, mouseY)) { - return buildTooltip("config.playercoordsapi.option.origin_port.tooltip", true); + return buildPortTooltip(); } } return List.of(); } + /** + * Returns the text field that should currently receive keyboard input. + */ private TextFieldWidget getFocusedTextField() { + if (apiPortField != null && apiPortField.isFocused()) { + return apiPortField; + } + for (OriginRow row : originRows) { if (row.hostField.visible && row.hostField.isFocused()) { return row.hostField; @@ -509,80 +698,230 @@ private TextFieldWidget getFocusedTextField() { return null; } + /** + * Builds the multi-line tooltip used by whitelist host fields. + */ private List buildHostTooltip() { List tooltip = new ArrayList<>(); tooltip.add(Text.translatable("config.playercoordsapi.option.origin_host.tooltip")); - tooltip.add(Text.translatable("config.playercoordsapi.option.origin_host.tooltip.localhost") - .append(Text.literal(" localhost").formatted(Formatting.GREEN))); - tooltip.add(Text.translatable("config.playercoordsapi.option.origin_host.tooltip.domain") - .append(Text.literal(" example.com").formatted(Formatting.GREEN))); - tooltip.add(Text.translatable("config.playercoordsapi.option.origin_host.tooltip.ipv4") - .append(Text.literal(" 127.0.0.1").formatted(Formatting.GREEN))); - tooltip.add(Text.translatable("config.playercoordsapi.option.origin_host.tooltip.ipv6") - .append(Text.literal(" 2001:db8::1").formatted(Formatting.GREEN))); + tooltip.add(buildHostTooltipLine("config.playercoordsapi.option.origin_host.tooltip.localhost", "localhost")); + tooltip.add(buildHostTooltipLine("config.playercoordsapi.option.origin_host.tooltip.domain", "example.com")); + tooltip.add(buildHostTooltipLine("config.playercoordsapi.option.origin_host.tooltip.ipv4", "127.0.0.1")); + tooltip.add(buildHostTooltipLine("config.playercoordsapi.option.origin_host.tooltip.ipv6", "2001:db8::1")); + appendWhitelistDisabledHint(tooltip); + return tooltip; + } - if (!isWhitelistEditable()) { - tooltip.add(Text.translatable("config.playercoordsapi.option.allowed_origins.disabled").formatted(Formatting.GRAY)); - } + /** + * Builds one highlighted example line for the host tooltip. + */ + private Text buildHostTooltipLine(String key, String example) { + return Text.translatable(key).append(Text.literal(" " + example).formatted(Formatting.GREEN)); + } + /** + * Builds the tooltip shown for the API port field. + */ + private List buildApiPortTooltip() { + List tooltip = new ArrayList<>(); + tooltip.add(Text.translatable("config.playercoordsapi.option.api_port.tooltip")); + tooltip.add(Text.translatable("config.playercoordsapi.option.api_port.tooltip.range") + .append(Text.literal(" " + ModConfig.MIN_API_PORT + "-" + ModConfig.MAX_API_PORT).formatted(Formatting.GREEN))); + tooltip.add(Text.translatable("config.playercoordsapi.option.api_port.tooltip.example") + .append(Text.literal(" 3000").formatted(Formatting.GREEN))); return tooltip; } - private List buildTooltip(String key, boolean includeWhitelistDisabledHint) { + /** + * Builds the tooltip shown for whitelist origin port fields. + */ + private List buildPortTooltip() { List tooltip = new ArrayList<>(); - tooltip.add(Text.translatable(key)); - - if (includeWhitelistDisabledHint && !isWhitelistEditable()) { - tooltip.add(Text.translatable("config.playercoordsapi.option.allowed_origins.disabled").formatted(Formatting.GRAY)); - } + tooltip.add(Text.translatable("config.playercoordsapi.option.origin_port.tooltip")); + tooltip.add(Text.translatable("config.playercoordsapi.option.origin_port.tooltip.example") + .append(Text.literal(" 3000").formatted(Formatting.GREEN))); + appendWhitelistDisabledHint(tooltip); + return tooltip; + } + /** + * Builds the tooltip for requests that provide an {@code Origin} header. + */ + private List buildWithOriginTooltip() { + List tooltip = new ArrayList<>(6); + tooltip.add(Text.translatable("config.playercoordsapi.option.cors_policy.tooltip.1")); + tooltip.add(Text.translatable("config.playercoordsapi.option.cors_policy.tooltip.2")); + tooltip.add(Text.translatable( + "config.playercoordsapi.option.cors_policy.tooltip.3", + emphasizeTooltipValue(Text.translatable("config.playercoordsapi.option.cors_policy.allow_all")) + )); + tooltip.add(Text.translatable( + "config.playercoordsapi.option.cors_policy.tooltip.4", + emphasizeTooltipValue(Text.translatable("config.playercoordsapi.option.cors_policy.local_web_apps_only")), + Text.literal("localhost").formatted(Formatting.GREEN), + Text.literal("127.0.0.1").formatted(Formatting.GREEN), + Text.literal("::1").formatted(Formatting.GREEN) + )); + tooltip.add(Text.translatable( + "config.playercoordsapi.option.cors_policy.tooltip.5", + emphasizeTooltipValue(Text.translatable("config.playercoordsapi.option.cors_policy.custom_whitelist")) + )); + tooltip.add(Text.translatable("config.playercoordsapi.option.cors_policy.tooltip.6")); return tooltip; } - private List buildTooltipLines(String keyPrefix, int lineCount, boolean includeWhitelistDisabledHint) { - List tooltip = new ArrayList<>(lineCount + 1); + /** + * Builds the tooltip for requests that do not provide an {@code Origin} header. + */ + private List buildWithoutOriginTooltip() { + List tooltip = new ArrayList<>(4); + tooltip.add(Text.translatable("config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1")); + tooltip.add(Text.translatable("config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2")); + tooltip.add(Text.translatable( + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3", + emphasizeTooltipValue(ScreenTexts.ON.copy()) + )); + tooltip.add(Text.translatable( + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4", + emphasizeTooltipValue(ScreenTexts.OFF.copy()), + emphasizeTooltipValue(Text.translatable("config.playercoordsapi.option.cors_policy")) + )); + return tooltip; + } - for (int i = 1; i <= lineCount; i++) { - tooltip.add(Text.translatable(keyPrefix + "." + i)); - } + /** + * Applies the shared visual emphasis used for important tooltip values. + */ + private Text emphasizeTooltipValue(Text text) { + return text.copy().formatted(Formatting.GRAY, Formatting.BOLD); + } - if (includeWhitelistDisabledHint && !isWhitelistEditable()) { + /** + * Appends the disabled-editing hint when the whitelist is visible but locked. + */ + private void appendWhitelistDisabledHint(List tooltip) { + if (!isWhitelistEditable()) { tooltip.add(Text.translatable("config.playercoordsapi.option.allowed_origins.disabled").formatted(Formatting.GRAY)); } + } - return tooltip; + /** + * Draws the top banner summarizing the HTTP server state. + */ + private void renderServerStatus(DrawContext context, int left) { + int boxTop = STATUS_BOX_Y; + int boxBottom = boxTop + STATUS_BOX_HEIGHT; + context.fill(left, boxTop, left + CONTENT_WIDTH, boxBottom, getServerStatusBackgroundColor()); + drawBorder(context, left, boxTop, CONTENT_WIDTH, STATUS_BOX_HEIGHT, getServerStatusBorderColor()); + context.drawCenteredTextWithShadow( + this.textRenderer, + getServerStatusText(), + this.width / 2, + boxTop + 4, + getServerStatusColor() + ); + } + + /** + * Returns the localized text shown inside the server status banner. + */ + private Text getServerStatusText() { + PlayerCoordsAPIClient.ServerStatus status = PlayerCoordsAPIClient.getServerStatus(); + + return switch (status.state()) { + case DISABLED -> Text.translatable("config.playercoordsapi.status.disabled"); + case STOPPED -> Text.translatable("config.playercoordsapi.status.stopped", status.port()); + case RUNNING -> Text.translatable("config.playercoordsapi.status.running", status.port()); + case FAILED -> Text.translatable("config.playercoordsapi.status.failed", status.port(), status.detail()); + }; } + /** + * Returns the banner text color for the current server state. + */ + private int getServerStatusColor() { + return switch (PlayerCoordsAPIClient.getServerStatus().state()) { + case DISABLED -> 0xFFD0D0D0; + case STOPPED -> 0xFFFFD070; + case RUNNING -> 0xFF90FF90; + case FAILED -> 0xFFFF9090; + }; + } + + /** + * Returns the banner background color for the current server state. + */ + private int getServerStatusBackgroundColor() { + return switch (PlayerCoordsAPIClient.getServerStatus().state()) { + case DISABLED -> 0x66303030; + case STOPPED -> 0x66504020; + case RUNNING -> 0x66204020; + case FAILED -> 0x66502020; + }; + } + + /** + * Returns the banner border color for the current server state. + */ + private int getServerStatusBorderColor() { + return switch (PlayerCoordsAPIClient.getServerStatus().state()) { + case DISABLED -> 0xFF606060; + case STOPPED -> 0xFFD0A040; + case RUNNING -> 0xFF55CC55; + case FAILED -> 0xFFFF5555; + }; + } + + /** + * Returns whether the mouse position lies inside the given rectangle. + */ private static boolean isWithin(double mouseX, double mouseY, int x, int y, int width, int height) { return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; } + /** + * Returns whether the cursor is hovering the active area of a setting label. + */ private static boolean isHoveringLabel(TextWidget label, int mouseX, int mouseY) { return isWithin(mouseX, mouseY, label.getX(), label.getY(), label.getWidth(), ROW_HEIGHT); } + /** + * Returns whether whitelist rows should currently be editable. + */ private boolean isWhitelistEditable() { return workingConfig.corsPolicy == ModConfig.CorsPolicy.CUSTOM_WHITELIST; } + /** + * Returns the localized label used by the with-origin policy cycling button. + */ private static Text getCorsPolicyLabel(ModConfig.CorsPolicy policy) { return Text.translatable("config.playercoordsapi.option.cors_policy." + policy.name().toLowerCase(Locale.ROOT)); } + /** + * Returns the localized label used by the origin scheme cycling button. + */ private static Text getOriginSchemeModeLabel(ModConfig.OriginSchemeMode mode) { return Text.translatable("config.playercoordsapi.option.origin_scheme." + mode.name().toLowerCase(Locale.ROOT)); } + /** + * Creates UI drafts from persisted entries or the legacy flat origin list. + */ private static List createDrafts(List originEntries, List origins) { List drafts = new ArrayList<>(); - if (originEntries != null && !originEntries.isEmpty()) { + if (originEntries != null) { for (ModConfig.OriginEntry originEntry : originEntries) { if (originEntry != null) { drafts.add(OriginDraft.fromConfigEntry(originEntry)); } } + } + if (!drafts.isEmpty()) { return drafts; } @@ -595,12 +934,18 @@ private static List createDrafts(List origin return drafts; } + /** + * One rendered whitelist row composed of scheme, host, port, and remove controls. + */ private final class OriginRow { private final CyclingButtonWidget schemeButton; private final TextFieldWidget hostField; private final TextFieldWidget portField; private final ButtonWidget removeButton; + /** + * Builds the widgets backing a single whitelist row. + */ private OriginRow(OriginDraft draft) { this.schemeButton = CyclingButtonWidget.builder(PlayerCoordsConfigScreen::getOriginSchemeModeLabel, draft.mode) .values(ModConfig.OriginSchemeMode.values()) @@ -634,6 +979,9 @@ private OriginRow(OriginDraft draft) { .build(); } + /** + * Updates visibility, editability, and focus behavior for this row. + */ private void setState(boolean visible, boolean editable) { schemeButton.visible = visible; schemeButton.active = visible && editable; @@ -659,6 +1007,9 @@ private void setState(boolean visible, boolean editable) { removeButton.active = visible && editable; } + /** + * Tries to focus one of the row text fields from the given mouse click. + */ private boolean tryFocusField(Click click, boolean doubleClick) { if (!hostField.visible || !hostField.active) { return false; @@ -667,6 +1018,7 @@ private boolean tryFocusField(Click click, boolean doubleClick) { if (hostField.isMouseOver(click.x(), click.y()) && hostField.mouseClicked(click, doubleClick)) { hostField.setFocused(true); portField.setFocused(false); + apiPortField.setFocused(false); PlayerCoordsConfigScreen.this.setFocused(hostField); return true; } @@ -674,6 +1026,7 @@ private boolean tryFocusField(Click click, boolean doubleClick) { if (portField.isMouseOver(click.x(), click.y()) && portField.mouseClicked(click, doubleClick)) { portField.setFocused(true); hostField.setFocused(false); + apiPortField.setFocused(false); PlayerCoordsConfigScreen.this.setFocused(portField); return true; } @@ -682,21 +1035,33 @@ private boolean tryFocusField(Click click, boolean doubleClick) { } } + /** + * Mutable UI representation of one whitelist entry before normalization and save. + */ private static final class OriginDraft { private ModConfig.OriginSchemeMode mode; private String host; private String port; + /** + * Creates an empty placeholder draft. + */ private OriginDraft() { this(ModConfig.OriginSchemeMode.AUTO, "", ""); } + /** + * Creates a draft with explicit values for all editable fields. + */ private OriginDraft(ModConfig.OriginSchemeMode mode, String host, String port) { this.mode = mode; this.host = host; this.port = port; } + /** + * Builds a draft from a persisted config entry. + */ private static OriginDraft fromConfigEntry(ModConfig.OriginEntry originEntry) { return new OriginDraft( originEntry.schemeMode == null ? ModConfig.OriginSchemeMode.AUTO : originEntry.schemeMode, @@ -705,6 +1070,9 @@ private static OriginDraft fromConfigEntry(ModConfig.OriginEntry originEntry) { ); } + /** + * Converts the draft back into its persisted config representation. + */ private ModConfig.OriginEntry toConfigEntry() { ModConfig.OriginEntry originEntry = new ModConfig.OriginEntry(); originEntry.schemeMode = mode; @@ -713,10 +1081,16 @@ private ModConfig.OriginEntry toConfigEntry() { return originEntry; } + /** + * Normalizes the draft into a canonical origin string when valid. + */ private Optional toNormalizedOrigin() { return CorsUtils.normalizeConfiguredOrigin(host, port, mode); } + /** + * Returns whether the draft is still just an empty placeholder row. + */ private boolean isEmpty() { return host == null || host.trim().isEmpty(); } diff --git a/src/client/java/fr/sukikui/playercoordsapi/mixin/client/PlayerCoordsAPIClientMixin.java b/src/client/java/fr/sukikui/playercoordsapi/mixin/client/PlayerCoordsAPIClientMixin.java index dd06d01..12ca884 100644 --- a/src/client/java/fr/sukikui/playercoordsapi/mixin/client/PlayerCoordsAPIClientMixin.java +++ b/src/client/java/fr/sukikui/playercoordsapi/mixin/client/PlayerCoordsAPIClientMixin.java @@ -6,11 +6,16 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +/** + * Empty template mixin kept as a starting point for future client-side injections. + */ @Mixin(MinecraftClient.class) public class PlayerCoordsAPIClientMixin { + /** + * No-op hook placeholder. + */ @Inject(at = @At("HEAD"), method = "run") private void init(CallbackInfo info) { - // This code is injected into the start of MinecraftClient.run() } -} \ No newline at end of file +} diff --git a/src/main/java/fr/sukikui/playercoordsapi/PlayerCoordsAPI.java b/src/main/java/fr/sukikui/playercoordsapi/PlayerCoordsAPI.java index c2d8d75..d62eec4 100644 --- a/src/main/java/fr/sukikui/playercoordsapi/PlayerCoordsAPI.java +++ b/src/main/java/fr/sukikui/playercoordsapi/PlayerCoordsAPI.java @@ -1,6 +1,5 @@ package fr.sukikui.playercoordsapi; -import fr.sukikui.playercoordsapi.config.CorsUtils; import fr.sukikui.playercoordsapi.config.ModConfig; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; @@ -8,50 +7,30 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Main mod entrypoint responsible for registering and sanitizing the shared config. + */ public class PlayerCoordsAPI implements ModInitializer { public static final String MOD_ID = "playercoordsapi"; - - // This logger is used to write text to the console and the log file. - // It is considered best practice to use your mod id as the logger's name. - // That way, it's clear which mod wrote info, warnings, and errors. public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - - // Config instance private static ModConfig config; + /** + * Registers the config serializer and normalizes persisted values once at startup. + */ @Override public void onInitialize() { - // Register config AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new); config = AutoConfig.getConfigHolder(ModConfig.class).getConfig(); + config.sanitize(); + AutoConfig.getConfigHolder(ModConfig.class).save(); - if (config.corsPolicy == null) { - config.corsPolicy = ModConfig.CorsPolicy.ALLOW_ALL; - } - - if (config.allowedOrigins == null) { - config.allowedOrigins = new java.util.ArrayList<>(ModConfig.DEFAULT_ALLOWED_ORIGINS); - } - - if (config.originEntries == null) { - config.originEntries = new java.util.ArrayList<>(); - } - - if (config.originEntries.isEmpty() && !config.allowedOrigins.isEmpty()) { - config.originEntries = CorsUtils.createConfiguredOriginEntries(config.allowedOrigins); - } - - config.allowedOrigins = config.originEntries.isEmpty() - ? CorsUtils.normalizeConfiguredOrigins(config.allowedOrigins) - : CorsUtils.normalizeConfiguredOriginsFromEntries(config.originEntries); - - // This code runs as soon as Minecraft is in a mod-load-ready state. - // However, some things (like resources) may still be uninitialized. - // Proceed with mild caution. - - LOGGER.info("PlayerCoordsAPI initialized - API will be available at http://localhost:25565/api/coords when enabled"); + LOGGER.info("PlayerCoordsAPI initialized - API will be available at http://localhost:{}/api/coords when enabled", config.apiPort); } + /** + * Returns the shared mutable config instance managed by AutoConfig. + */ public static ModConfig getConfig() { return config; } diff --git a/src/main/java/fr/sukikui/playercoordsapi/config/CorsUtils.java b/src/main/java/fr/sukikui/playercoordsapi/config/CorsUtils.java index 4225e4e..f75a9cc 100644 --- a/src/main/java/fr/sukikui/playercoordsapi/config/CorsUtils.java +++ b/src/main/java/fr/sukikui/playercoordsapi/config/CorsUtils.java @@ -7,10 +7,13 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.Locale; +/** + * Utility methods for normalizing, validating, and comparing configured origins. + */ public final class CorsUtils { private static final String HTTP_SCHEME = "http"; private static final String HTTPS_SCHEME = "https"; @@ -18,14 +21,23 @@ public final class CorsUtils { private CorsUtils() { } + /** + * Normalizes a request {@code Origin} header if it is valid. + */ public static Optional normalizeOrigin(String rawOrigin) { return normalizeOrigin(rawOrigin, false); } + /** + * Normalizes a configured origin, allowing implicit scheme inference. + */ public static Optional normalizeConfiguredOrigin(String rawOrigin) { return normalizeOrigin(rawOrigin, true); } + /** + * Builds and normalizes an origin from split host, port, and scheme-mode fields. + */ public static Optional normalizeConfiguredOrigin(String host, String port, ModConfig.OriginSchemeMode schemeMode) { String authority = buildAuthority(host, port); @@ -40,6 +52,9 @@ public static Optional normalizeConfiguredOrigin(String host, String por }; } + /** + * Validates and canonicalizes an origin string. + */ private static Optional normalizeOrigin(String rawOrigin, boolean allowImplicitScheme) { if (rawOrigin == null) { return Optional.empty(); @@ -111,6 +126,9 @@ private static Optional normalizeOrigin(String rawOrigin, boolean allowI return Optional.of(normalizedScheme + "://" + hostForOutput + ":" + port); } + /** + * Infers a scheme for configured origins that omit one. + */ private static Optional inferOriginWithScheme(String rawOrigin) { String authority = prepareAuthority(rawOrigin); URI authorityUri; @@ -131,6 +149,9 @@ private static Optional inferOriginWithScheme(String rawOrigin) { return Optional.of(inferredScheme + "://" + authority); } + /** + * Converts raw host input into a URI authority, wrapping plain IPv6 addresses when needed. + */ private static String prepareAuthority(String rawOrigin) { String authority = rawOrigin.startsWith("//") ? rawOrigin.substring(2) : rawOrigin; @@ -141,6 +162,9 @@ private static String prepareAuthority(String rawOrigin) { return authority; } + /** + * Returns whether a host string points to a loopback address. + */ private static boolean isLoopbackHost(String host) { String normalizedHost = stripIpv6Brackets(host).toLowerCase(Locale.ROOT); @@ -150,6 +174,9 @@ private static boolean isLoopbackHost(String host) { || normalizedHost.startsWith("127."); } + /** + * Infers the default scheme to use for configured authorities without one. + */ private static String inferScheme(String host, int port) { if (port == 80) { return HTTP_SCHEME; @@ -162,6 +189,9 @@ private static String inferScheme(String host, int port) { return isLoopbackHost(host) ? HTTP_SCHEME : HTTPS_SCHEME; } + /** + * Builds a normalized authority string from the UI host/port inputs. + */ private static String buildAuthority(String host, String port) { String trimmedHost = host == null ? "" : host.trim(); @@ -194,6 +224,9 @@ private static String buildAuthority(String host, String port) { return authorityHost + ":" + trimmedPort; } + /** + * Removes square brackets from IPv6 literals when present. + */ private static String stripIpv6Brackets(String host) { if (host == null) { return null; @@ -206,6 +239,9 @@ private static String stripIpv6Brackets(String host) { return host; } + /** + * Checks whether the given origin is allowed by the current policy. + */ public static boolean isOriginAllowed(ModConfig config, String origin) { Optional normalizedOrigin = normalizeOrigin(origin); ModConfig.CorsPolicy corsPolicy = config.corsPolicy == null @@ -226,6 +262,9 @@ public static boolean isOriginAllowed(ModConfig config, String origin) { }; } + /** + * Resolves whether an origin points to a loopback host after normalization. + */ public static boolean isLoopbackOrigin(String origin) { Optional normalizedOrigin = normalizeOrigin(origin); @@ -251,6 +290,9 @@ public static boolean isLoopbackOrigin(String origin) { } } + /** + * Normalizes and deduplicates a persisted list of origin strings. + */ public static List normalizeConfiguredOrigins(List origins) { Set normalizedOrigins = new LinkedHashSet<>(); @@ -261,6 +303,9 @@ public static List normalizeConfiguredOrigins(List origins) { return new ArrayList<>(normalizedOrigins); } + /** + * Normalizes and deduplicates the origin list represented by config entries. + */ public static List normalizeConfiguredOriginsFromEntries(List originEntries) { Set normalizedOrigins = new LinkedHashSet<>(); @@ -276,6 +321,9 @@ public static List normalizeConfiguredOriginsFromEntries(List(normalizedOrigins); } + /** + * Converts normalized origin strings into editable config entries. + */ public static List createConfiguredOriginEntries(List origins) { List originEntries = new ArrayList<>(); @@ -286,6 +334,9 @@ public static List createConfiguredOriginEntries(List createConfiguredOriginEntry(String origin) { Optional normalizedOrigin = normalizeOrigin(origin); @@ -318,18 +369,4 @@ public static Optional createConfiguredOriginEntry(String return Optional.empty(); } } - - public static boolean hasDuplicateOrigins(List origins) { - Set normalizedOrigins = new LinkedHashSet<>(); - - for (String origin : origins) { - Optional normalizedOrigin = normalizeConfiguredOrigin(origin); - - if (normalizedOrigin.isPresent() && !normalizedOrigins.add(normalizedOrigin.get())) { - return true; - } - } - - return false; - } } diff --git a/src/main/java/fr/sukikui/playercoordsapi/config/ModConfig.java b/src/main/java/fr/sukikui/playercoordsapi/config/ModConfig.java index 310172f..90ad547 100644 --- a/src/main/java/fr/sukikui/playercoordsapi/config/ModConfig.java +++ b/src/main/java/fr/sukikui/playercoordsapi/config/ModConfig.java @@ -6,30 +6,110 @@ import java.util.ArrayList; import java.util.List; +import java.util.OptionalInt; +/** + * Persistent mod configuration used by the runtime server and config screen. + */ @Config(name = PlayerCoordsAPI.MOD_ID) public class ModConfig implements ConfigData { + public static final int DEFAULT_API_PORT = 25565; + public static final int MIN_API_PORT = 1; + public static final int MAX_API_PORT = 65535; public static final List DEFAULT_ALLOWED_ORIGINS = List.of(); + /** + * Defines how requests with an {@code Origin} header are filtered. + */ public enum CorsPolicy { ALLOW_ALL, LOCAL_WEB_APPS_ONLY, CUSTOM_WHITELIST } + /** + * Controls how whitelist entries infer or force their scheme. + */ public enum OriginSchemeMode { AUTO, HTTP, HTTPS } + /** + * Editable whitelist entry stored in config and mirrored by the custom UI. + */ public static class OriginEntry { public OriginSchemeMode schemeMode = OriginSchemeMode.AUTO; public String host = ""; public String port = ""; } + /** + * Returns whether the API port is inside the valid TCP user range handled by the UI. + */ + public static boolean isValidApiPort(int port) { + return port >= MIN_API_PORT && port <= MAX_API_PORT; + } + + /** + * Falls back to the default API port when the persisted value is invalid. + */ + public static int normalizeApiPort(int port) { + return isValidApiPort(port) ? port : DEFAULT_API_PORT; + } + + /** + * Parses a raw port string coming from text inputs. + */ + public static OptionalInt parseApiPort(String rawPort) { + if (rawPort == null) { + return OptionalInt.empty(); + } + + String trimmedPort = rawPort.trim(); + + if (trimmedPort.isEmpty()) { + return OptionalInt.empty(); + } + + try { + int port = Integer.parseInt(trimmedPort); + return isValidApiPort(port) ? OptionalInt.of(port) : OptionalInt.empty(); + } catch (NumberFormatException e) { + return OptionalInt.empty(); + } + } + + /** + * Normalizes nullable, legacy, or partially-invalid persisted config values. + */ + public void sanitize() { + if (corsPolicy == null) { + corsPolicy = CorsPolicy.ALLOW_ALL; + } + + if (allowedOrigins == null) { + allowedOrigins = new ArrayList<>(DEFAULT_ALLOWED_ORIGINS); + } + + if (originEntries == null) { + originEntries = new ArrayList<>(); + } + + apiPort = normalizeApiPort(apiPort); + + if (originEntries.isEmpty() && !allowedOrigins.isEmpty()) { + originEntries = CorsUtils.createConfiguredOriginEntries(allowedOrigins); + } + + allowedOrigins = originEntries.isEmpty() + ? CorsUtils.normalizeConfiguredOrigins(allowedOrigins) + : CorsUtils.normalizeConfiguredOriginsFromEntries(originEntries); + } + public boolean enabled = true; + public int apiPort = DEFAULT_API_PORT; public CorsPolicy corsPolicy = CorsPolicy.ALLOW_ALL; public boolean allowNonBrowserLocalClients = true; public List allowedOrigins = new ArrayList<>(DEFAULT_ALLOWED_ORIGINS); diff --git a/src/main/java/fr/sukikui/playercoordsapi/mixin/PlayerCoordsAPIMixin.java b/src/main/java/fr/sukikui/playercoordsapi/mixin/PlayerCoordsAPIMixin.java index ccd8d8d..36e676c 100644 --- a/src/main/java/fr/sukikui/playercoordsapi/mixin/PlayerCoordsAPIMixin.java +++ b/src/main/java/fr/sukikui/playercoordsapi/mixin/PlayerCoordsAPIMixin.java @@ -6,11 +6,16 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +/** + * Empty template mixin kept as a starting point for future common-side injections. + */ @Mixin(MinecraftServer.class) public class PlayerCoordsAPIMixin { + /** + * No-op hook placeholder. + */ @Inject(at = @At("HEAD"), method = "loadWorld") private void init(CallbackInfo info) { - // This code is injected into the start of MinecraftServer.loadWorld() } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/playercoordsapi/lang/de_de.json b/src/main/resources/assets/playercoordsapi/lang/de_de.json index b147332..9e617cb 100644 --- a/src/main/resources/assets/playercoordsapi/lang/de_de.json +++ b/src/main/resources/assets/playercoordsapi/lang/de_de.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Anwenden", + "config.playercoordsapi.button.reset": "Zurücksetzen", + "config.playercoordsapi.status.disabled": "API deaktiviert", + "config.playercoordsapi.status.stopped": "API gestoppt (Port %s)", + "config.playercoordsapi.status.running": "API läuft auf Port %s", + "config.playercoordsapi.status.failed": "API auf Port %s nicht verfügbar: %s", "config.playercoordsapi.option.enabled": "Mod aktivieren", - "config.playercoordsapi.option.cors_policy": "CORS-Richtlinie", + "config.playercoordsapi.option.api_port": "API-Port", + "config.playercoordsapi.option.api_port.tooltip": "Port des lokalen API-Servers.", + "config.playercoordsapi.option.api_port.tooltip.range": "Erlaubter Bereich:", + "config.playercoordsapi.option.api_port.tooltip.example": "Beispiel:", + "config.playercoordsapi.option.cors_policy": "Mit Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Alles erlauben", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Lokale Web-Apps", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Benutzerdefinierte Whitelist", - "config.playercoordsapi.option.cors_policy.tooltip.1": "Die API bleibt nur von diesem Rechner aus erreichbar.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Diese Option legt fest, welche Websites sie im Browser lesen dürfen.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Alles erlauben: akzeptiert alle Web-Origins.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Lokale Web-Apps: akzeptiert nur localhost, 127.0.0.1 und ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Benutzerdefinierte Whitelist: akzeptiert nur die unten hinzugefügten Origins.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Gilt nicht für Clients ohne Origin-Header.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Clients ohne Browser", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Erlaubt oder blockiert lokale Clients ohne Origin-Header.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Beispiele: curl, Skripte, Desktop-Apps und native Tools.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "An: Diese lokalen Clients dürfen die API aufrufen.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Aus: Nur Browser oder Web-Apps, die durch die CORS-Richtlinie erlaubt sind, dürfen sie nutzen.", - "config.playercoordsapi.option.allowed_origins": "Erlaubte Origins", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Lokale Origins", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Whitelist", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Steuert Anfragen, die einen Origin-Header senden.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Beispiele: Webseiten, Browser-Fetch oder Anwendungen, die diesen Header explizit hinzufügen.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: akzeptiert jede Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: akzeptiert nur %s, %s und %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: akzeptiert nur die unten aufgeführten Origins.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "Die API bleibt in jedem Fall auf diesen Rechner beschränkt.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Ohne Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Steuert lokale Anfragen ohne Origin-Header.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Beispiele: direkte URL, curl, Skripte, Desktop-Anwendungen und native Tools.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: diese Anfragen können die API aufrufen.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: nur Anfragen, die durch die Einstellung %s erlaubt sind, können sie nutzen.", "config.playercoordsapi.option.add_origin": "Erlaubte Origin hinzufügen", - "config.playercoordsapi.option.origin_scheme": "Schema", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Port", - "config.playercoordsapi.option.origin_port.tooltip": "Optionaler Port, zum Beispiel 3000. Leer lassen, um den Standardport zu verwenden.", - "config.playercoordsapi.option.allowed_origins.disabled": "Stelle die CORS-Richtlinie auf Benutzerdefinierte Whitelist, um diese Liste zu bearbeiten.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Gib eine gültige Origin ein.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Füge mindestens eine Origin hinzu.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Entferne doppelte Origins." + "config.playercoordsapi.option.origin_port.tooltip": "Optionaler Port.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Beispiel:", + "config.playercoordsapi.option.allowed_origins.disabled": "Stelle Mit Origin auf Whitelist, um diese Liste zu bearbeiten." } diff --git a/src/main/resources/assets/playercoordsapi/lang/en_us.json b/src/main/resources/assets/playercoordsapi/lang/en_us.json index 42df378..1f77f0e 100644 --- a/src/main/resources/assets/playercoordsapi/lang/en_us.json +++ b/src/main/resources/assets/playercoordsapi/lang/en_us.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Apply", + "config.playercoordsapi.button.reset": "Reset", + "config.playercoordsapi.status.disabled": "API disabled", + "config.playercoordsapi.status.stopped": "API stopped (port %s)", + "config.playercoordsapi.status.running": "API running on port %s", + "config.playercoordsapi.status.failed": "API unavailable on port %s: %s", "config.playercoordsapi.option.enabled": "Enable Mod", - "config.playercoordsapi.option.cors_policy": "CORS Policy", + "config.playercoordsapi.option.api_port": "API Port", + "config.playercoordsapi.option.api_port.tooltip": "Port used by the local API server.", + "config.playercoordsapi.option.api_port.tooltip.range": "Allowed range:", + "config.playercoordsapi.option.api_port.tooltip.example": "Example:", + "config.playercoordsapi.option.cors_policy": "With Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Allow all", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Local web apps", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Custom whitelist", - "config.playercoordsapi.option.cors_policy.tooltip.1": "The API is still only reachable from this machine.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "This setting controls which websites can read it from a browser.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Allow all: accepts every web origin.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Local web apps: only accepts localhost, 127.0.0.1, and ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Custom whitelist: only accepts the origins listed below.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Does not apply to clients without an Origin header.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Non-browser clients", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Allows or blocks local clients without an Origin header.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Examples: curl, scripts, desktop apps, and native tools.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "On: those local clients can call the API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Off: only browsers or web apps allowed by the CORS policy can use it.", - "config.playercoordsapi.option.allowed_origins": "Allowed origins", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Local origins", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Whitelist", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Controls requests that send an Origin header.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Examples: web pages, browser fetch, or apps that explicitly add this header.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: accepts every origin.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: only accepts %s, %s, and %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: only accepts the origins listed below.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "The API still stays local to this machine.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Without Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Controls local requests without an Origin header.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Examples: direct URL, curl, scripts, desktop apps, and native tools.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: those requests can call the API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: only requests allowed by the %s setting can use it.", "config.playercoordsapi.option.add_origin": "Add allowed origin", - "config.playercoordsapi.option.origin_scheme": "Scheme", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Port", - "config.playercoordsapi.option.origin_port.tooltip": "Optional port, for example 3000. Leave empty to use the default port.", - "config.playercoordsapi.option.allowed_origins.disabled": "Switch CORS Policy to Custom whitelist to edit this list.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Enter a valid origin.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Add at least one origin.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Remove duplicate origins." + "config.playercoordsapi.option.origin_port.tooltip": "Optional port.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Example:", + "config.playercoordsapi.option.allowed_origins.disabled": "Set With Origin to Whitelist to edit this list." } diff --git a/src/main/resources/assets/playercoordsapi/lang/es_es.json b/src/main/resources/assets/playercoordsapi/lang/es_es.json index 78d9c72..5b6c6c1 100644 --- a/src/main/resources/assets/playercoordsapi/lang/es_es.json +++ b/src/main/resources/assets/playercoordsapi/lang/es_es.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Aplicar", + "config.playercoordsapi.button.reset": "Restablecer", + "config.playercoordsapi.status.disabled": "API desactivada", + "config.playercoordsapi.status.stopped": "API detenida (puerto %s)", + "config.playercoordsapi.status.running": "API activa en el puerto %s", + "config.playercoordsapi.status.failed": "API no disponible en el puerto %s: %s", "config.playercoordsapi.option.enabled": "Activar mod", - "config.playercoordsapi.option.cors_policy": "Política CORS", + "config.playercoordsapi.option.api_port": "Puerto de la API", + "config.playercoordsapi.option.api_port.tooltip": "Puerto usado por el servidor API local.", + "config.playercoordsapi.option.api_port.tooltip.range": "Rango permitido:", + "config.playercoordsapi.option.api_port.tooltip.example": "Ejemplo:", + "config.playercoordsapi.option.cors_policy": "Con Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Permitir todo", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Apps web locales", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Lista blanca personalizada", - "config.playercoordsapi.option.cors_policy.tooltip.1": "La API sigue siendo accesible solo desde esta máquina.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Este ajuste define qué sitios web pueden leerla desde un navegador.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Permitir todo: acepta cualquier origen web.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Apps web locales: solo acepta localhost, 127.0.0.1 y ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Lista blanca: solo acepta los orígenes añadidos abajo.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "No se aplica a clientes sin cabecera Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Clientes sin navegador", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Permite o bloquea clientes locales sin cabecera Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Ejemplos: curl, scripts, aplicaciones de escritorio y herramientas nativas.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "Activado: esos clientes locales pueden llamar a la API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Desactivado: solo pueden usarla navegadores o apps web permitidos por la política CORS.", - "config.playercoordsapi.option.allowed_origins": "Orígenes permitidos", - "config.playercoordsapi.option.add_origin": "Añadir origen permitido", - "config.playercoordsapi.option.origin_scheme": "Esquema", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Orígenes locales", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Lista blanca", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Controla las solicitudes que envían un encabezado Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Ejemplos: páginas web, fetch del navegador o aplicaciones que agregan este encabezado explícitamente.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: acepta todos los orígenes.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: solo acepta %s, %s y %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: solo acepta los orígenes listados abajo.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "La API sigue limitada a esta máquina en todos los casos.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Sin Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Controla las solicitudes locales sin encabezado Origin.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Ejemplos: URL directa, curl, scripts, aplicaciones de escritorio y herramientas nativas.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: esas solicitudes pueden llamar a la API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: solo las solicitudes permitidas por el ajuste %s pueden usarla.", + "config.playercoordsapi.option.add_origin": "Agregar origen permitido", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Puerto", - "config.playercoordsapi.option.origin_port.tooltip": "Puerto opcional, por ejemplo 3000. Déjalo vacío para usar el puerto predeterminado.", - "config.playercoordsapi.option.allowed_origins.disabled": "Cambia la política CORS a Lista blanca personalizada para editar esta lista.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Introduce un origen válido.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Añade al menos un origen.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Elimina los orígenes duplicados." + "config.playercoordsapi.option.origin_port.tooltip": "Puerto opcional.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Ejemplo:", + "config.playercoordsapi.option.allowed_origins.disabled": "Pon Con Origin en Lista blanca para editar esta lista." } diff --git a/src/main/resources/assets/playercoordsapi/lang/fr_fr.json b/src/main/resources/assets/playercoordsapi/lang/fr_fr.json index 6680d37..a21659c 100644 --- a/src/main/resources/assets/playercoordsapi/lang/fr_fr.json +++ b/src/main/resources/assets/playercoordsapi/lang/fr_fr.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Appliquer", + "config.playercoordsapi.button.reset": "Réinit.", + "config.playercoordsapi.status.disabled": "API désactivée", + "config.playercoordsapi.status.stopped": "API arrêtée (port %s)", + "config.playercoordsapi.status.running": "API active sur le port %s", + "config.playercoordsapi.status.failed": "API indisponible sur le port %s : %s", "config.playercoordsapi.option.enabled": "Activer le mod", - "config.playercoordsapi.option.cors_policy": "Politique CORS", + "config.playercoordsapi.option.api_port": "Port de l'API", + "config.playercoordsapi.option.api_port.tooltip": "Port utilisé par le serveur API local.", + "config.playercoordsapi.option.api_port.tooltip.range": "Plage autorisée :", + "config.playercoordsapi.option.api_port.tooltip.example": "Exemple :", + "config.playercoordsapi.option.cors_policy": "Avec Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Autoriser tout", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Apps web locales", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Liste blanche personnalisée", - "config.playercoordsapi.option.cors_policy.tooltip.1": "L'API reste accessible uniquement depuis cette machine.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Ce réglage définit quels sites web peuvent la lire depuis un navigateur.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Autoriser tout : accepte toutes les origines web.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Apps web locales : accepte seulement localhost, 127.0.0.1 et ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Liste blanche : accepte seulement les origines ajoutées ci-dessous.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Ne s'applique pas aux clients sans en-tête Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Clients hors navigateur", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Autorise ou bloque les clients locaux sans en-tête Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Exemples : curl, scripts, applications desktop, outils natifs.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "Activé : ces clients locaux peuvent appeler l'API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Désactivé : seuls les navigateurs ou apps web autorisés peuvent l'utiliser.", - "config.playercoordsapi.option.allowed_origins": "Origines autorisées", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Origines locales", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Liste blanche", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Contrôle les requêtes qui envoient un en-tête Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Exemples : pages web, fetch navigateur ou applications qui ajoutent explicitement cet en-tête.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s : accepte toutes les origines.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s : accepte seulement %s, %s et %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s : accepte seulement les origines ajoutées ci-dessous.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "L'API reste dans tous les cas limitée à cette machine.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Sans Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Contrôle les requêtes locales sans en-tête Origin.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Exemples : URL directe, curl, scripts, applications desktop et outils natifs.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s : ces requêtes peuvent appeler l'API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s : seules les requêtes autorisées par le réglage %s peuvent l'utiliser.", "config.playercoordsapi.option.add_origin": "Ajouter une origine autorisée", - "config.playercoordsapi.option.origin_scheme": "Schéma", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4 :", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6 :", "config.playercoordsapi.option.origin_port": "Port", - "config.playercoordsapi.option.origin_port.tooltip": "Port facultatif, par exemple 3000. Laissez vide pour utiliser le port par défaut.", - "config.playercoordsapi.option.allowed_origins.disabled": "Passez la politique CORS sur Liste blanche personnalisée pour modifier cette liste.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Saisissez une origine valide.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Ajoutez au moins une origine.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Supprimez les origines en double." + "config.playercoordsapi.option.origin_port.tooltip": "Port facultatif.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Exemple :", + "config.playercoordsapi.option.allowed_origins.disabled": "Passez le réglage Avec Origin sur Liste blanche pour modifier cette liste." } diff --git a/src/main/resources/assets/playercoordsapi/lang/it_it.json b/src/main/resources/assets/playercoordsapi/lang/it_it.json index c05ac1c..b313d19 100644 --- a/src/main/resources/assets/playercoordsapi/lang/it_it.json +++ b/src/main/resources/assets/playercoordsapi/lang/it_it.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Applica", + "config.playercoordsapi.button.reset": "Reimposta", + "config.playercoordsapi.status.disabled": "API disattivata", + "config.playercoordsapi.status.stopped": "API arrestata (porta %s)", + "config.playercoordsapi.status.running": "API attiva sulla porta %s", + "config.playercoordsapi.status.failed": "API non disponibile sulla porta %s: %s", "config.playercoordsapi.option.enabled": "Attiva mod", - "config.playercoordsapi.option.cors_policy": "Criterio CORS", + "config.playercoordsapi.option.api_port": "Porta API", + "config.playercoordsapi.option.api_port.tooltip": "Porta usata dal server API locale.", + "config.playercoordsapi.option.api_port.tooltip.range": "Intervallo consentito:", + "config.playercoordsapi.option.api_port.tooltip.example": "Esempio:", + "config.playercoordsapi.option.cors_policy": "Con Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Consenti tutto", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "App web locali", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Whitelist personalizzata", - "config.playercoordsapi.option.cors_policy.tooltip.1": "L'API resta raggiungibile solo da questa macchina.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Questa opzione definisce quali siti web possono leggerla da un browser.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Consenti tutto: accetta qualsiasi origine web.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "App web locali: accetta solo localhost, 127.0.0.1 e ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Whitelist personalizzata: accetta solo le origini aggiunte qui sotto.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Non si applica ai client senza header Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Client senza browser", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Consente o blocca i client locali senza header Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Esempi: curl, script, applicazioni desktop e strumenti nativi.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "Attivo: questi client locali possono chiamare l'API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Disattivo: possono usarla solo browser o app web consentiti dalla politica CORS.", - "config.playercoordsapi.option.allowed_origins": "Origini consentite", - "config.playercoordsapi.option.add_origin": "Aggiungi origine consentita", - "config.playercoordsapi.option.origin_scheme": "Schema", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Origin locali", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Whitelist", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Controlla le richieste che inviano un header Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Esempi: pagine web, fetch del browser o applicazioni che aggiungono esplicitamente questo header.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: accetta ogni origin.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: accetta solo %s, %s e %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: accetta solo le origin elencate qui sotto.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "L'API resta comunque limitata a questa macchina.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Senza Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Controlla le richieste locali senza header Origin.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Esempi: URL diretta, curl, script, applicazioni desktop e strumenti nativi.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: queste richieste possono chiamare l'API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: solo le richieste consentite dall'impostazione %s possono usarla.", + "config.playercoordsapi.option.add_origin": "Aggiungi origin consentita", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Porta", - "config.playercoordsapi.option.origin_port.tooltip": "Porta facoltativa, per esempio 3000. Lasciala vuota per usare la porta predefinita.", - "config.playercoordsapi.option.allowed_origins.disabled": "Imposta la politica CORS su Whitelist personalizzata per modificare questa lista.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Inserisci un'origine valida.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Aggiungi almeno un'origine.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Rimuovi le origini duplicate." + "config.playercoordsapi.option.origin_port.tooltip": "Porta facoltativa.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Esempio:", + "config.playercoordsapi.option.allowed_origins.disabled": "Imposta Con Origin su Whitelist per modificare questo elenco." } diff --git a/src/main/resources/assets/playercoordsapi/lang/ja_jp.json b/src/main/resources/assets/playercoordsapi/lang/ja_jp.json index 2cedef8..846a5da 100644 --- a/src/main/resources/assets/playercoordsapi/lang/ja_jp.json +++ b/src/main/resources/assets/playercoordsapi/lang/ja_jp.json @@ -1,37 +1,43 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "適用", + "config.playercoordsapi.button.reset": "リセット", + "config.playercoordsapi.status.disabled": "APIは無効です", + "config.playercoordsapi.status.stopped": "APIは停止中です (ポート %s)", + "config.playercoordsapi.status.running": "APIはポート %s で動作中です", + "config.playercoordsapi.status.failed": "ポート %s でAPIを利用できません: %s", "config.playercoordsapi.option.enabled": "Modを有効化", - "config.playercoordsapi.option.cors_policy": "CORSポリシー", + "config.playercoordsapi.option.api_port": "APIポート", + "config.playercoordsapi.option.api_port.tooltip": "ローカルAPIサーバーが使用するポートです。", + "config.playercoordsapi.option.api_port.tooltip.range": "許可される範囲:", + "config.playercoordsapi.option.api_port.tooltip.example": "例:", + "config.playercoordsapi.option.cors_policy": "Originあり", "config.playercoordsapi.option.cors_policy.allow_all": "すべて許可", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "ローカル Web アプリ", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "カスタム許可リスト", - "config.playercoordsapi.option.cors_policy.tooltip.1": "API はこのマシンからのみ引き続き利用できます。", - "config.playercoordsapi.option.cors_policy.tooltip.2": "この設定は、ブラウザーからどの Web サイトが読み取れるかを決めます。", - "config.playercoordsapi.option.cors_policy.tooltip.3": "すべて許可: すべての Web Origin を受け入れます。", - "config.playercoordsapi.option.cors_policy.tooltip.4": "ローカル Web アプリ: localhost、127.0.0.1、::1 のみ受け入れます。", - "config.playercoordsapi.option.cors_policy.tooltip.5": "カスタム許可リスト: 下に追加した Origin のみ受け入れます。", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Origin ヘッダーのないクライアントには適用されません。", - "config.playercoordsapi.option.allow_non_browser_local_clients": "ブラウザー外クライアント", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Origin ヘッダーのないローカルクライアントを許可または拒否します。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "例: curl、スクリプト、デスクトップアプリ、ネイティブツール。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "オン: それらのローカルクライアントは API を呼び出せます。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "オフ: CORS ポリシーで許可されたブラウザーまたは Web アプリだけが利用できます。", - "config.playercoordsapi.option.allowed_origins": "許可された Origin", - "config.playercoordsapi.option.add_origin": "許可する Origin を追加", - "config.playercoordsapi.option.origin_scheme": "スキーム", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "ローカルOrigin", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "ホワイトリスト", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Originヘッダーを送るリクエストを制御します。", + "config.playercoordsapi.option.cors_policy.tooltip.2": "例: Webページ、ブラウザーのfetch、またはこのヘッダーを明示的に追加するアプリ。", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: すべてのOriginを受け入れます。", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: %s、%s、%s だけを受け入れます。", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: 下の一覧にあるOriginだけを受け入れます。", + "config.playercoordsapi.option.cors_policy.tooltip.6": "どの場合でもAPIはこのマシン内に限定されます。", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Originなし", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Originヘッダーがないローカルリクエストを制御します。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "例: 直接URL、curl、スクリプト、デスクトップアプリ、ネイティブツール。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: それらのリクエストはAPIを呼び出せます。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: %s の設定で許可されたリクエストだけが利用できます。", + "config.playercoordsapi.option.add_origin": "許可するOriginを追加", "config.playercoordsapi.option.origin_scheme.auto": "自動", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", "config.playercoordsapi.option.origin_host": "ホスト / IP", - "config.playercoordsapi.option.origin_host.tooltip": "ホスト名または IP アドレスです。", - "config.playercoordsapi.option.origin_host.tooltip.localhost": "ローカルホスト:", + "config.playercoordsapi.option.origin_host.tooltip": "ホスト名またはIPアドレス。", + "config.playercoordsapi.option.origin_host.tooltip.localhost": "Localhost:", "config.playercoordsapi.option.origin_host.tooltip.domain": "ドメイン:", "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "ポート", - "config.playercoordsapi.option.origin_port.tooltip": "任意のポートです。例: 3000。空欄の場合は既定のポートを使います。", - "config.playercoordsapi.option.allowed_origins.disabled": "この一覧を編集するには、CORS ポリシーをカスタム許可リストに切り替えてください。", - "config.playercoordsapi.option.allowed_origins.error.invalid": "有効な Origin を入力してください。", - "config.playercoordsapi.option.allowed_origins.error.empty": "少なくとも 1 つの Origin を追加してください。", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "重複した Origin を削除してください。" + "config.playercoordsapi.option.origin_port.tooltip": "任意のポート。", + "config.playercoordsapi.option.origin_port.tooltip.example": "例:", + "config.playercoordsapi.option.allowed_origins.disabled": "この一覧を編集するには、Originあり を ホワイトリスト に設定してください。" } diff --git a/src/main/resources/assets/playercoordsapi/lang/pt_br.json b/src/main/resources/assets/playercoordsapi/lang/pt_br.json index f3ab13b..9f24062 100644 --- a/src/main/resources/assets/playercoordsapi/lang/pt_br.json +++ b/src/main/resources/assets/playercoordsapi/lang/pt_br.json @@ -1,37 +1,43 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Aplicar", + "config.playercoordsapi.button.reset": "Redefinir", + "config.playercoordsapi.status.disabled": "API desativada", + "config.playercoordsapi.status.stopped": "API parada (porta %s)", + "config.playercoordsapi.status.running": "API ativa na porta %s", + "config.playercoordsapi.status.failed": "API indisponível na porta %s: %s", "config.playercoordsapi.option.enabled": "Ativar mod", - "config.playercoordsapi.option.cors_policy": "Política CORS", + "config.playercoordsapi.option.api_port": "Porta da API", + "config.playercoordsapi.option.api_port.tooltip": "Porta usada pelo servidor API local.", + "config.playercoordsapi.option.api_port.tooltip.range": "Intervalo permitido:", + "config.playercoordsapi.option.api_port.tooltip.example": "Exemplo:", + "config.playercoordsapi.option.cors_policy": "Com Origin", "config.playercoordsapi.option.cors_policy.allow_all": "Permitir tudo", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Apps web locais", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Lista personalizada", - "config.playercoordsapi.option.cors_policy.tooltip.1": "A API continua acessível apenas nesta máquina.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Esta opção define quais sites podem lê-la em um navegador.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Permitir tudo: aceita qualquer origem web.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Apps web locais: aceita apenas localhost, 127.0.0.1 e ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Lista personalizada: aceita apenas as origens adicionadas abaixo.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Não se aplica a clientes sem o cabeçalho Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Clientes sem navegador", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Permite ou bloqueia clientes locais sem o cabeçalho Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Exemplos: curl, scripts, aplicativos desktop e ferramentas nativas.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "Ativado: esses clientes locais podem chamar a API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Desativado: só navegadores ou apps web permitidos pela política CORS podem usá-la.", - "config.playercoordsapi.option.allowed_origins": "Origens permitidas", - "config.playercoordsapi.option.add_origin": "Adicionar origem permitida", - "config.playercoordsapi.option.origin_scheme": "Esquema", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Origens locais", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Lista branca", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Controla requisições que enviam um cabeçalho Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Exemplos: páginas web, fetch do navegador ou aplicativos que adicionam esse cabeçalho explicitamente.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: aceita qualquer origin.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: aceita apenas %s, %s e %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: aceita apenas as origins listadas abaixo.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "A API continua restrita a esta máquina em todos os casos.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Sem Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Controla requisições locais sem cabeçalho Origin.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Exemplos: URL direta, curl, scripts, aplicativos desktop e ferramentas nativas.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: essas requisições podem chamar a API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: apenas as requisições permitidas pela opção %s podem usá-la.", + "config.playercoordsapi.option.add_origin": "Adicionar origin permitida", "config.playercoordsapi.option.origin_scheme.auto": "Auto", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", "config.playercoordsapi.option.origin_host": "Host / IP", "config.playercoordsapi.option.origin_host.tooltip": "Nome do host ou endereço IP.", "config.playercoordsapi.option.origin_host.tooltip.localhost": "Localhost:", - "config.playercoordsapi.option.origin_host.tooltip.domain": "Domínio:", + "config.playercoordsapi.option.origin_host.tooltip.domain": "Dominio:", "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Porta", - "config.playercoordsapi.option.origin_port.tooltip": "Porta opcional, por exemplo 3000. Deixe em branco para usar a porta padrão.", - "config.playercoordsapi.option.allowed_origins.disabled": "Altere a política CORS para Lista personalizada para editar esta lista.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Insira uma origem válida.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Adicione pelo menos uma origem.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Remova origens duplicadas." + "config.playercoordsapi.option.origin_port.tooltip": "Porta opcional.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Exemplo:", + "config.playercoordsapi.option.allowed_origins.disabled": "Defina Com Origin como Lista branca para editar esta lista." } diff --git a/src/main/resources/assets/playercoordsapi/lang/ru_ru.json b/src/main/resources/assets/playercoordsapi/lang/ru_ru.json index b2bab56..37efc85 100644 --- a/src/main/resources/assets/playercoordsapi/lang/ru_ru.json +++ b/src/main/resources/assets/playercoordsapi/lang/ru_ru.json @@ -1,24 +1,32 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "Применить", + "config.playercoordsapi.button.reset": "Сбросить", + "config.playercoordsapi.status.disabled": "API отключен", + "config.playercoordsapi.status.stopped": "API остановлен (порт %s)", + "config.playercoordsapi.status.running": "API активен на порту %s", + "config.playercoordsapi.status.failed": "API недоступен на порту %s: %s", "config.playercoordsapi.option.enabled": "Включить мод", - "config.playercoordsapi.option.cors_policy": "Политика CORS", - "config.playercoordsapi.option.cors_policy.allow_all": "Разрешить всё", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Локальные веб-приложения", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "Пользовательский белый список", - "config.playercoordsapi.option.cors_policy.tooltip.1": "API по-прежнему доступно только с этого компьютера.", - "config.playercoordsapi.option.cors_policy.tooltip.2": "Этот параметр определяет, какие сайты могут читать его в браузере.", - "config.playercoordsapi.option.cors_policy.tooltip.3": "Разрешить всё: принимает любые web-origin.", - "config.playercoordsapi.option.cors_policy.tooltip.4": "Локальные веб-приложения: принимает только localhost, 127.0.0.1 и ::1.", - "config.playercoordsapi.option.cors_policy.tooltip.5": "Пользовательский белый список: принимает только origins, добавленные ниже.", - "config.playercoordsapi.option.cors_policy.tooltip.6": "Не применяется к клиентам без заголовка Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients": "Клиенты вне браузера", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Разрешает или блокирует локальных клиентов без заголовка Origin.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Примеры: curl, скрипты, desktop-приложения и нативные инструменты.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "Вкл.: эти локальные клиенты могут вызывать API.", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "Выкл.: пользоваться API смогут только браузеры или web-приложения, разрешённые политикой CORS.", - "config.playercoordsapi.option.allowed_origins": "Разрешённые origins", - "config.playercoordsapi.option.add_origin": "Добавить origin", - "config.playercoordsapi.option.origin_scheme": "Схема", + "config.playercoordsapi.option.api_port": "Порт API", + "config.playercoordsapi.option.api_port.tooltip": "Порт локального API-сервера.", + "config.playercoordsapi.option.api_port.tooltip.range": "Допустимый диапазон:", + "config.playercoordsapi.option.api_port.tooltip.example": "Пример:", + "config.playercoordsapi.option.cors_policy": "С Origin", + "config.playercoordsapi.option.cors_policy.allow_all": "Разрешить все", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "Локальные Origin", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "Белый список", + "config.playercoordsapi.option.cors_policy.tooltip.1": "Управляет запросами, которые отправляют заголовок Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.2": "Примеры: веб-страницы, browser fetch или приложения, которые явно добавляют этот заголовок.", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: принимает любые Origin.", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: принимает только %s, %s и %s.", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: принимает только Origin, перечисленные ниже.", + "config.playercoordsapi.option.cors_policy.tooltip.6": "Во всех случаях API остается доступным только на этой машине.", + "config.playercoordsapi.option.allow_non_browser_local_clients": "Без Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "Управляет локальными запросами без заголовка Origin.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "Примеры: прямой URL, curl, скрипты, десктопные приложения и нативные инструменты.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: такие запросы могут вызывать API.", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: использовать API могут только запросы, разрешенные параметром %s.", + "config.playercoordsapi.option.add_origin": "Добавить разрешенный Origin", "config.playercoordsapi.option.origin_scheme.auto": "Авто", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", @@ -29,9 +37,7 @@ "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "Порт", - "config.playercoordsapi.option.origin_port.tooltip": "Необязательный порт, например 3000. Оставьте пустым, чтобы использовать порт по умолчанию.", - "config.playercoordsapi.option.allowed_origins.disabled": "Переключите политику CORS на Пользовательский белый список, чтобы редактировать этот список.", - "config.playercoordsapi.option.allowed_origins.error.invalid": "Введите корректный origin.", - "config.playercoordsapi.option.allowed_origins.error.empty": "Добавьте хотя бы один origin.", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "Удалите повторяющиеся origins." + "config.playercoordsapi.option.origin_port.tooltip": "Необязательный порт.", + "config.playercoordsapi.option.origin_port.tooltip.example": "Пример:", + "config.playercoordsapi.option.allowed_origins.disabled": "Установите С Origin на Белый список, чтобы редактировать этот список." } diff --git a/src/main/resources/assets/playercoordsapi/lang/zh_cn.json b/src/main/resources/assets/playercoordsapi/lang/zh_cn.json index c0ad2b8..e5c58bf 100644 --- a/src/main/resources/assets/playercoordsapi/lang/zh_cn.json +++ b/src/main/resources/assets/playercoordsapi/lang/zh_cn.json @@ -1,37 +1,43 @@ { "text.autoconfig.playercoordsapi.title": "PlayerCoordsAPI", + "config.playercoordsapi.button.apply": "应用", + "config.playercoordsapi.button.reset": "重置", + "config.playercoordsapi.status.disabled": "API已禁用", + "config.playercoordsapi.status.stopped": "API已停止 (端口 %s)", + "config.playercoordsapi.status.running": "API正在端口 %s 上运行", + "config.playercoordsapi.status.failed": "端口 %s 上的API不可用: %s", "config.playercoordsapi.option.enabled": "启用模组", - "config.playercoordsapi.option.cors_policy": "CORS 策略", - "config.playercoordsapi.option.cors_policy.allow_all": "允许全部", - "config.playercoordsapi.option.cors_policy.local_web_apps_only": "本地 Web 应用", - "config.playercoordsapi.option.cors_policy.custom_whitelist": "自定义白名单", - "config.playercoordsapi.option.cors_policy.tooltip.1": "该 API 仍然只能从这台机器访问。", - "config.playercoordsapi.option.cors_policy.tooltip.2": "此设置决定哪些网站可以在浏览器中读取它。", - "config.playercoordsapi.option.cors_policy.tooltip.3": "允许全部:接受所有 Web Origin。", - "config.playercoordsapi.option.cors_policy.tooltip.4": "本地 Web 应用:仅接受 localhost、127.0.0.1 和 ::1。", - "config.playercoordsapi.option.cors_policy.tooltip.5": "自定义白名单:仅接受下方添加的来源。", - "config.playercoordsapi.option.cors_policy.tooltip.6": "不适用于没有 Origin 标头的客户端。", - "config.playercoordsapi.option.allow_non_browser_local_clients": "非浏览器客户端", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "允许或阻止没有 Origin 标头的本地客户端。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "例如:curl、脚本、桌面应用和原生工具。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "开启:这些本地客户端可以调用该 API。", - "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "关闭:只有被 CORS 策略允许的浏览器或 Web 应用才能使用它。", - "config.playercoordsapi.option.allowed_origins": "允许的来源", - "config.playercoordsapi.option.add_origin": "添加允许的来源", - "config.playercoordsapi.option.origin_scheme": "协议", + "config.playercoordsapi.option.api_port": "API端口", + "config.playercoordsapi.option.api_port.tooltip": "本地API服务器使用的端口。", + "config.playercoordsapi.option.api_port.tooltip.range": "允许范围:", + "config.playercoordsapi.option.api_port.tooltip.example": "示例:", + "config.playercoordsapi.option.cors_policy": "有 Origin", + "config.playercoordsapi.option.cors_policy.allow_all": "全部允许", + "config.playercoordsapi.option.cors_policy.local_web_apps_only": "本地 Origin", + "config.playercoordsapi.option.cors_policy.custom_whitelist": "白名单", + "config.playercoordsapi.option.cors_policy.tooltip.1": "控制发送 Origin 头的请求。", + "config.playercoordsapi.option.cors_policy.tooltip.2": "示例: 网页、浏览器 fetch,或显式添加此头的应用。", + "config.playercoordsapi.option.cors_policy.tooltip.3": "%s: 接受所有 Origin。", + "config.playercoordsapi.option.cors_policy.tooltip.4": "%s: 仅接受 %s、%s 和 %s。", + "config.playercoordsapi.option.cors_policy.tooltip.5": "%s: 仅接受下方列出的 Origin。", + "config.playercoordsapi.option.cors_policy.tooltip.6": "无论如何,API都只限于这台机器。", + "config.playercoordsapi.option.allow_non_browser_local_clients": "无 Origin", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.1": "控制没有 Origin 头的本地请求。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.2": "示例: 直接URL、curl、脚本、桌面应用和原生工具。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.3": "%s: 这些请求可以调用API。", + "config.playercoordsapi.option.allow_non_browser_local_clients.tooltip.4": "%s: 只有被 %s 设置允许的请求才能使用它。", + "config.playercoordsapi.option.add_origin": "添加允许的 Origin", "config.playercoordsapi.option.origin_scheme.auto": "自动", "config.playercoordsapi.option.origin_scheme.http": "HTTP", "config.playercoordsapi.option.origin_scheme.https": "HTTPS", "config.playercoordsapi.option.origin_host": "主机 / IP", - "config.playercoordsapi.option.origin_host.tooltip": "主机名或 IP 地址。", - "config.playercoordsapi.option.origin_host.tooltip.localhost": "本地主机:", + "config.playercoordsapi.option.origin_host.tooltip": "主机名或IP地址。", + "config.playercoordsapi.option.origin_host.tooltip.localhost": "Localhost:", "config.playercoordsapi.option.origin_host.tooltip.domain": "域名:", "config.playercoordsapi.option.origin_host.tooltip.ipv4": "IPv4:", "config.playercoordsapi.option.origin_host.tooltip.ipv6": "IPv6:", "config.playercoordsapi.option.origin_port": "端口", - "config.playercoordsapi.option.origin_port.tooltip": "可选端口,例如 3000。留空时使用默认端口。", - "config.playercoordsapi.option.allowed_origins.disabled": "将 CORS 策略切换为自定义白名单后才能编辑此列表。", - "config.playercoordsapi.option.allowed_origins.error.invalid": "请输入有效的来源。", - "config.playercoordsapi.option.allowed_origins.error.empty": "至少添加一个来源。", - "config.playercoordsapi.option.allowed_origins.error.duplicate": "请移除重复的来源。" + "config.playercoordsapi.option.origin_port.tooltip": "可选端口。", + "config.playercoordsapi.option.origin_port.tooltip.example": "示例:", + "config.playercoordsapi.option.allowed_origins.disabled": "将 有 Origin 设置为 白名单 以编辑此列表。" } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index dc385d9..5498c77 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -42,8 +42,5 @@ }, "recommends": { "modmenu": ">=${modmenu_version}" - }, - "suggests": { - "another-mod": "*" } }