mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-24 03:33:46 -04:00
使用宏自动从英文文档复制部分内容 (#4406)
This commit is contained in:
parent
24fd2cf521
commit
480f8b6890
@ -97,38 +97,46 @@ public record Document(DocumentFileTree directory,
|
||||
|
||||
String macroName = matcher.group("name");
|
||||
String endLine = "<!-- #END " + macroName + " -->";
|
||||
var properties = new LinkedHashMap<String, List<String>>();
|
||||
var lines = new ArrayList<String>();
|
||||
while (true) {
|
||||
line = reader.readLine();
|
||||
|
||||
if (line == null)
|
||||
throw new IOException("Missing end line for macro: " + macroName);
|
||||
else if (line.startsWith("<!-- #END")) {
|
||||
if (line.equals(endLine)) {
|
||||
break;
|
||||
} else {
|
||||
throw new IOException("Invalid macro end line: " + line);
|
||||
}
|
||||
} else {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
var properties = new LinkedHashMap<String, List<String>>();
|
||||
int propertiesCount = 0;
|
||||
|
||||
// Handle properties
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.startsWith("<!-- #") || line.equals(endLine))
|
||||
for (String macroBodyLine : lines) {
|
||||
if (!macroBodyLine.startsWith("<!-- #"))
|
||||
break;
|
||||
|
||||
Matcher propertyMatcher = MACRO_PROPERTY_LINE.matcher(line);
|
||||
Matcher propertyMatcher = MACRO_PROPERTY_LINE.matcher(macroBodyLine);
|
||||
if (propertyMatcher.matches()) {
|
||||
String propertyName = propertyMatcher.group("name");
|
||||
String propertyValue = parsePropertyValue(propertyMatcher.group("value"));
|
||||
|
||||
properties.computeIfAbsent(propertyName, k -> new ArrayList<>(1))
|
||||
.add(propertyValue);
|
||||
propertiesCount++;
|
||||
} else {
|
||||
throw new IOException("Invalid macro property line: " + line);
|
||||
throw new IOException("Invalid macro property line: " + macroBodyLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle lines
|
||||
if (line != null && !line.equals(endLine)) {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("<!-- #"))
|
||||
break;
|
||||
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (line == null || !line.equals(endLine))
|
||||
throw new IOException("Invalid macro end line: " + line);
|
||||
if (propertiesCount > 0)
|
||||
lines.subList(0, propertiesCount).clear();
|
||||
|
||||
items.add(new MacroBlock(macroName,
|
||||
Collections.unmodifiableMap(properties),
|
||||
|
@ -17,14 +17,64 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import javax.print.Doc;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/// Macro processor for automatically updating documentation.
|
||||
///
|
||||
/// Users can use the macro processor in `.md` documents within the `docs` folder and its subfolders.
|
||||
/// The parts to be processed should be wrapped with `<!-- #BEGIN MACRO_NAME -->` and `<!-- #END MACRO_NAME -->` lines.
|
||||
///
|
||||
/// For example, if you create a document `FOO.md` and translate it into Simplified Chinese, Traditional Chinese, and Japanese,
|
||||
/// you can add the following content in these files to create links to other language versions:
|
||||
///
|
||||
/// ```markdown
|
||||
/// <!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
/// <!-- #END LANGUAGE_SWITCHER -->
|
||||
/// ```
|
||||
///
|
||||
/// After running `./gradlew updateDocuments`, the macro processor will automatically update the content between these two lines:
|
||||
///
|
||||
/// ```
|
||||
/// <!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
/// **English** | 中文 ([简体](FOO_zh.md), [繁體](FOO_zh_Hant.md)) | [日本語](FOO_ja.md)
|
||||
/// <!-- #END LANGUAGE_SWITCHER -->
|
||||
/// ```
|
||||
///
|
||||
/// @author Glavo
|
||||
public enum MacroProcessor {
|
||||
/// Does not process the content in any way.
|
||||
///
|
||||
/// Supported properties:
|
||||
///
|
||||
/// - `NAME`: The name of this block (used by other macros).
|
||||
/// - `PROCESS_LINK`: If set to `FALSE`, document links in the content will not be automatically updated.
|
||||
BLOCK {
|
||||
@Override
|
||||
public void apply(Document document, Document.MacroBlock macroBlock, StringBuilder outputBuilder) throws IOException {
|
||||
var mutableProperties = new LinkedHashMap<>(macroBlock.properties());
|
||||
MacroProcessor.removeSingleProperty(mutableProperties, "NAME");
|
||||
boolean processLink = !"FALSE".equalsIgnoreCase(MacroProcessor.removeSingleProperty(mutableProperties, "PROCESS_LINK"));
|
||||
|
||||
if (!mutableProperties.isEmpty())
|
||||
throw new IllegalArgumentException("Unsupported properties: " + mutableProperties.keySet());
|
||||
|
||||
MacroProcessor.writeBegin(outputBuilder, macroBlock);
|
||||
MacroProcessor.writeProperties(outputBuilder, macroBlock);
|
||||
for (String line : macroBlock.contentLines()) {
|
||||
if (processLink)
|
||||
MacroProcessor.processLine(outputBuilder, line, document);
|
||||
else
|
||||
outputBuilder.append(line).append('\n');
|
||||
}
|
||||
MacroProcessor.writeEnd(outputBuilder, macroBlock);
|
||||
}
|
||||
},
|
||||
|
||||
/// Used to automatically generate links to other language versions of the current document.
|
||||
///
|
||||
/// Does not support any properties.
|
||||
LANGUAGE_SWITCHER {
|
||||
private static <T> boolean containsIdentity(List<T> list, T element) {
|
||||
for (T t : list) {
|
||||
@ -104,17 +154,187 @@ public enum MacroProcessor {
|
||||
MacroProcessor.writeEnd(outputBuilder, macroBlock);
|
||||
}
|
||||
},
|
||||
BLOCK {
|
||||
|
||||
/// Copy the block with the specified name from the English version of the current document.
|
||||
///
|
||||
/// Supported properties:
|
||||
///
|
||||
/// - `NAME` (required): Specifies the block to be copied.
|
||||
/// - `REPLACE` (repeatable): Used to replace specified text. Accepts a list containing two strings. The first string is a regular expression for matching content; the second string is the replacement target.
|
||||
/// - `PROCESS_LINK`: If set to `FALSE`, document links in the content will not be automatically updated.
|
||||
COPY {
|
||||
private record Replace(Pattern pattern, String replacement) {
|
||||
}
|
||||
|
||||
private static IllegalArgumentException illegalReplace(String value) {
|
||||
return new IllegalArgumentException("Illegal replacement pattern: " + value);
|
||||
}
|
||||
|
||||
private static Replace parseReplace(String value) {
|
||||
List<String> list = MacroProcessor.parseStringList(value);
|
||||
if (list.size() != 2)
|
||||
throw illegalReplace(value);
|
||||
|
||||
return new Replace(Pattern.compile(list.get(0)), list.get(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Document document, Document.MacroBlock macroBlock, StringBuilder outputBuilder) throws IOException {
|
||||
var mutableProperties = new LinkedHashMap<>(macroBlock.properties());
|
||||
String blockName = MacroProcessor.removeSingleProperty(mutableProperties, "NAME");
|
||||
if (blockName == null)
|
||||
throw new IllegalArgumentException("Missing property: NAME");
|
||||
|
||||
List<Replace> replaces = Objects.requireNonNullElse(mutableProperties.remove("REPLACE"), List.<String>of())
|
||||
.stream()
|
||||
.map(it -> parseReplace(it))
|
||||
.toList();
|
||||
|
||||
boolean processLink = !"FALSE".equalsIgnoreCase(MacroProcessor.removeSingleProperty(mutableProperties, "PROCESS_LINK"));
|
||||
|
||||
if (!mutableProperties.isEmpty())
|
||||
throw new IllegalArgumentException("Unsupported properties: " + mutableProperties.keySet());
|
||||
|
||||
LocalizedDocument localizedDocument = document.directory().getFiles().get(document.name());
|
||||
Document fromDocument;
|
||||
if (localizedDocument == null || (fromDocument = localizedDocument.getDocuments().get(DocumentLocale.ENGLISH)) == null)
|
||||
throw new IOException("Document " + document.name() + " for english does not exist");
|
||||
|
||||
List<String> nameList = List.of(blockName);
|
||||
|
||||
var fromBlock = (Document.MacroBlock) fromDocument.items().stream()
|
||||
.filter(it -> it instanceof Document.MacroBlock macro
|
||||
&& macro.name().equals(BLOCK.name())
|
||||
&& nameList.equals(macro.properties().get("NAME"))
|
||||
)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IOException("Cannot find the block \"" + blockName + "\" in " + fromDocument.file()));
|
||||
|
||||
MacroProcessor.writeBegin(outputBuilder, macroBlock);
|
||||
MacroProcessor.writeProperties(outputBuilder, macroBlock);
|
||||
for (String line : macroBlock.contentLines()) {
|
||||
outputBuilder.append(line).append('\n');
|
||||
for (String line : fromBlock.contentLines()) {
|
||||
for (Replace replace : replaces) {
|
||||
line = replace.pattern.matcher(line).replaceAll(replace.replacement());
|
||||
}
|
||||
if (processLink)
|
||||
processLine(outputBuilder, line, document);
|
||||
else
|
||||
outputBuilder.append(line).append('\n');
|
||||
}
|
||||
MacroProcessor.writeEnd(outputBuilder, macroBlock);
|
||||
}
|
||||
};
|
||||
},
|
||||
;
|
||||
|
||||
private static String removeSingleProperty(Map<String, List<String>> properties, String name) {
|
||||
List<String> values = properties.remove(name);
|
||||
if (values == null || values.isEmpty())
|
||||
return null;
|
||||
|
||||
if (values.size() != 1)
|
||||
throw new IllegalArgumentException("Unexpected number of property " + name + ": " + values.size());
|
||||
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
private static List<String> parseStringList(String str) {
|
||||
if (str.isBlank()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// Split the string with ' and space cleverly.
|
||||
ArrayList<String> parts = new ArrayList<>(2);
|
||||
|
||||
boolean hasValue = false;
|
||||
StringBuilder current = new StringBuilder(str.length());
|
||||
for (int i = 0; i < str.length(); ) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\'' || c == '"') {
|
||||
hasValue = true;
|
||||
int end = str.indexOf(c, i + 1);
|
||||
if (end < 0) {
|
||||
end = str.length();
|
||||
}
|
||||
current.append(str, i + 1, end);
|
||||
i = end + 1;
|
||||
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
if (hasValue) {
|
||||
parts.add(current.toString());
|
||||
current.setLength(0);
|
||||
hasValue = false;
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
hasValue = true;
|
||||
current.append(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (hasValue)
|
||||
parts.add(current.toString());
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private static final Pattern LINK_PATTERN = Pattern.compile(
|
||||
"(?<=]\\()[a-zA-Z0-9_\\-./]+\\.md(?=\\))"
|
||||
);
|
||||
|
||||
static void processLine(StringBuilder outputBuilder, String line, Document document) {
|
||||
outputBuilder.append(LINK_PATTERN.matcher(line).replaceAll(matchResult -> {
|
||||
String rawLink = matchResult.group();
|
||||
String[] splitPath = rawLink.split("/");
|
||||
|
||||
if (splitPath.length == 0)
|
||||
return rawLink;
|
||||
|
||||
String fileName = splitPath[splitPath.length - 1];
|
||||
if (!fileName.endsWith(".md"))
|
||||
return rawLink;
|
||||
|
||||
DocumentFileTree current = document.directory();
|
||||
for (int i = 0; i < splitPath.length - 1; i++) {
|
||||
String name = splitPath[i];
|
||||
switch (name) {
|
||||
case "" -> {
|
||||
return rawLink;
|
||||
}
|
||||
case "." -> {
|
||||
continue;
|
||||
}
|
||||
case ".." -> {
|
||||
current = current.getParent();
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
default -> {
|
||||
current = current.getChildren().get(name);
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DocumentLocale.LocaleAndName currentLocaleAndName = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - ".md".length()));
|
||||
LocalizedDocument localizedDocument = current.getFiles().get(currentLocaleAndName.name());
|
||||
if (localizedDocument != null) {
|
||||
List<DocumentLocale> candidateLocales = document.locale().getCandidates();
|
||||
for (DocumentLocale candidateLocale : candidateLocales) {
|
||||
if (candidateLocale == currentLocaleAndName.locale())
|
||||
return rawLink;
|
||||
|
||||
Document targetDoc = localizedDocument.getDocuments().get(candidateLocale);
|
||||
if (targetDoc != null) {
|
||||
splitPath[splitPath.length - 1] = targetDoc.file().getFileName().toString();
|
||||
return String.join("/", splitPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rawLink;
|
||||
})).append('\n');
|
||||
}
|
||||
|
||||
private static void writeBegin(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {
|
||||
builder.append("<!-- #BEGIN ");
|
||||
|
@ -25,8 +25,6 @@ import org.gradle.api.tasks.TaskAction;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/// @author Glavo
|
||||
public abstract class UpdateDocuments extends DefaultTask {
|
||||
@ -36,71 +34,12 @@ public abstract class UpdateDocuments extends DefaultTask {
|
||||
|
||||
// ---
|
||||
|
||||
private static final Pattern LINK_PATTERN = Pattern.compile(
|
||||
"(?<=]\\()[a-zA-Z0-9_\\-./]+\\.md(?=\\))"
|
||||
);
|
||||
|
||||
private void processLine(StringBuilder outputBuilder, String line, Document document) {
|
||||
outputBuilder.append(LINK_PATTERN.matcher(line).replaceAll(matchResult -> {
|
||||
String rawLink = matchResult.group();
|
||||
String[] splitPath = rawLink.split("/");
|
||||
|
||||
if (splitPath.length == 0)
|
||||
return rawLink;
|
||||
|
||||
String fileName = splitPath[splitPath.length - 1];
|
||||
if (!fileName.endsWith(".md"))
|
||||
return rawLink;
|
||||
|
||||
DocumentFileTree current = document.directory();
|
||||
for (int i = 0; i < splitPath.length - 1; i++) {
|
||||
String name = splitPath[i];
|
||||
switch (name) {
|
||||
case "" -> {
|
||||
return rawLink;
|
||||
}
|
||||
case "." -> {
|
||||
continue;
|
||||
}
|
||||
case ".." -> {
|
||||
current = current.getParent();
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
default -> {
|
||||
current = current.getChildren().get(name);
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DocumentLocale.LocaleAndName currentLocaleAndName = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - ".md".length()));
|
||||
LocalizedDocument localizedDocument = current.getFiles().get(currentLocaleAndName.name());
|
||||
if (localizedDocument != null) {
|
||||
List<DocumentLocale> candidateLocales = document.locale().getCandidates();
|
||||
for (DocumentLocale candidateLocale : candidateLocales) {
|
||||
if (candidateLocale == currentLocaleAndName.locale())
|
||||
return rawLink;
|
||||
|
||||
Document targetDoc = localizedDocument.getDocuments().get(candidateLocale);
|
||||
if (targetDoc != null) {
|
||||
splitPath[splitPath.length - 1] = targetDoc.file().getFileName().toString();
|
||||
return String.join("/", splitPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rawLink;
|
||||
})).append('\n');
|
||||
}
|
||||
|
||||
private void updateDocument(Document document) throws IOException {
|
||||
StringBuilder outputBuilder = new StringBuilder(8192);
|
||||
|
||||
for (Document.Item item : document.items()) {
|
||||
if (item instanceof Document.Line line) {
|
||||
processLine(outputBuilder, line.content(), document);
|
||||
MacroProcessor.processLine(outputBuilder, line.content(), document);
|
||||
} else if (item instanceof Document.MacroBlock macro) {
|
||||
var processor = MacroProcessor.valueOf(macro.name());
|
||||
processor.apply(document, macro, outputBuilder);
|
||||
|
@ -4,6 +4,8 @@
|
||||
**English** | 中文 ([简体](PLATFORM_zh.md), [繁體](PLATFORM_zh_Hant.md))
|
||||
<!-- #END LANGUAGE_SWITCHER -->
|
||||
|
||||
<!-- #BEGIN BLOCK -->
|
||||
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
|
||||
@ -16,6 +18,7 @@
|
||||
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
<!-- #END BLOCK -->
|
||||
|
||||
Legend:
|
||||
|
||||
|
@ -4,18 +4,23 @@
|
||||
[English](PLATFORM.md) | **中文** (**简体**, [繁體](PLATFORM_zh_Hant.md))
|
||||
<!-- #END LANGUAGE_SWITCHER -->
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
|
||||
<!-- #PROPERTY REPLACE="\\(Old World\\)" "(旧世界)" -->
|
||||
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
<!-- #END COPY -->
|
||||
|
||||
图例:
|
||||
|
||||
|
@ -4,18 +4,23 @@
|
||||
[English](PLATFORM.md) | **中文** ([简体](PLATFORM_zh.md), **繁體**)
|
||||
<!-- #END LANGUAGE_SWITCHER -->
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
|
||||
<!-- #PROPERTY REPLACE="\\(Old World\\)" "(舊世界)" -->
|
||||
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
<!-- #END COPY -->
|
||||
|
||||
圖例:
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN BLOCK -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END BLOCK -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
**English** | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,14 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | **español** | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | **日本語** | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | **中文** ([简体](README_zh.md), [繁體](README_zh_Hant.md), **文言**) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | **русский** | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | 中文 ([简体](README_zh.md), [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | **українська**
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | **中文** (**简体**, [繁體](README_zh_Hant.md), [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Hello Minecraft! Launcher
|
||||
|
||||
<!-- #BEGIN COPY -->
|
||||
<!-- #PROPERTY NAME=BADGES -->
|
||||
[](https://ci.huangyuhui.net/job/HMCL)
|
||||

|
||||

|
||||
[](https://discord.gg/jVvC7HfM6U)
|
||||
[](https://docs.hmcl.net/groups.html)
|
||||
<!-- #END COPY -->
|
||||
|
||||
<!-- #BEGIN LANGUAGE_SWITCHER -->
|
||||
[English](README.md) | **中文** ([简体](README_zh.md), **繁體**, [文言](README_lzh.md)) | [日本語](README_ja.md) | [español](README_es.md) | [русский](README_ru.md) | [українська](README_uk.md)
|
||||
|
Loading…
x
Reference in New Issue
Block a user