mirror of
https://github.com/songoda/SongodaCore.git
synced 2024-11-27 04:25:19 +01:00
Provisional first implementation of the new localization system
It it not done yet. A lot of usability features are still missing. Including a proper interface to interact with the whole new system in the plugins.
This commit is contained in:
parent
2e15ed5d28
commit
b168ad0738
@ -0,0 +1,101 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.http.HttpClient;
|
||||
import com.songoda.core.http.HttpResponse;
|
||||
import com.songoda.core.http.UnexpectedHttpStatusException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class LocaleFileManager {
|
||||
private final HttpClient httpClient;
|
||||
private final String projectName;
|
||||
|
||||
public LocaleFileManager(HttpClient httpClient, String projectName) {
|
||||
this.httpClient = httpClient;
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public List<String> downloadMissingTranslations(File targetDirectory) throws IOException {
|
||||
List<String> availableLanguages = this.fetchAvailableLanguageFiles();
|
||||
if (availableLanguages == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Files.createDirectories(targetDirectory.toPath());
|
||||
|
||||
List<String> downloadedLocales = new LinkedList<>();
|
||||
for (String languageFileName : availableLanguages) {
|
||||
File languageFile = new File(targetDirectory, languageFileName);
|
||||
if (languageFile.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String languageFileContents = fetchProjectFile(languageFileName);
|
||||
if (languageFileContents == null) {
|
||||
throw new IOException("Failed to download language file " + languageFileName); // TODO: Better exception
|
||||
}
|
||||
|
||||
try (Writer writer = new FileWriter(languageFile)) {
|
||||
writer.write(languageFileContents);
|
||||
}
|
||||
|
||||
downloadedLocales.add(languageFileName);
|
||||
}
|
||||
|
||||
return downloadedLocales;
|
||||
}
|
||||
|
||||
public @Nullable List<String> fetchAvailableLanguageFiles() throws IOException {
|
||||
String projectLanguageIndex = fetchProjectFile("_index.txt");
|
||||
|
||||
if (projectLanguageIndex == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> result = new LinkedList<>();
|
||||
|
||||
for (String line : projectLanguageIndex.split("\r?\n")) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line.startsWith("#") && !line.isEmpty()) {
|
||||
result.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String fetchProjectFile(String fileName) throws IOException {
|
||||
String url = formatUrl("https://songoda.github.io/Translations/projects/%s/%s", this.projectName, fileName);
|
||||
HttpResponse httpResponse = this.httpClient.get(url);
|
||||
|
||||
if (httpResponse.getResponseCode() == 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (httpResponse.getResponseCode() != 200) {
|
||||
throw new UnexpectedHttpStatusException(httpResponse.getResponseCode(), url);
|
||||
}
|
||||
|
||||
return httpResponse.getBodyAsString();
|
||||
}
|
||||
|
||||
private static String formatUrl(String url, Object... params) throws UnsupportedEncodingException {
|
||||
Object[] encodedParams = new Object[params.length];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
encodedParams[i] = URLEncoder.encode(params[i].toString(), "UTF-8");
|
||||
}
|
||||
|
||||
return String.format(url, encodedParams);
|
||||
}
|
||||
}
|
115
Core/src/main/java/com/songoda/core/locale/LocaleManager.java
Normal file
115
Core/src/main/java/com/songoda/core/locale/LocaleManager.java
Normal file
@ -0,0 +1,115 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.configuration.songoda.SongodaYamlConfig;
|
||||
import com.songoda.core.configuration.yaml.YamlConfiguration;
|
||||
import com.songoda.core.http.SimpleHttpClient;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class LocaleManager {
|
||||
protected final Plugin plugin;
|
||||
protected final File localesDirectory;
|
||||
|
||||
protected final List<SongodaYamlConfig> loadedLocales = new LinkedList<>();
|
||||
protected final @Nullable YamlConfiguration fallbackLocale;
|
||||
|
||||
public LocaleManager(Plugin plugin) throws IOException {
|
||||
this.plugin = plugin;
|
||||
this.localesDirectory = new File(this.plugin.getDataFolder(), "locales");
|
||||
|
||||
this.fallbackLocale = loadFallbackLocale();
|
||||
}
|
||||
|
||||
public List<String> downloadMissingLocales() {
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(new SimpleHttpClient(), this.plugin.getName());
|
||||
|
||||
try {
|
||||
return localeFileManager.downloadMissingTranslations(this.localesDirectory);
|
||||
} catch (IOException ex) {
|
||||
this.plugin.getLogger().warning("Failed to download missing locales: " + ex.getMessage());
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void load(String locale) throws IOException {
|
||||
File fileToLoad = determineAvailableLocaleVariation(locale);
|
||||
if (fileToLoad == null) {
|
||||
throw new FileNotFoundException("Locale file " + locale + " not found");
|
||||
}
|
||||
|
||||
for (SongodaYamlConfig loadedLocale : this.loadedLocales) {
|
||||
if (loadedLocale.file.equals(fileToLoad)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongodaYamlConfig localeConfig = new SongodaYamlConfig(fileToLoad);
|
||||
localeConfig.load();
|
||||
this.loadedLocales.add(localeConfig);
|
||||
}
|
||||
|
||||
public void loadExclusively(String locale) {
|
||||
loadExclusively(Collections.singletonList(locale));
|
||||
}
|
||||
|
||||
public void loadExclusively(List<String> locales) {
|
||||
unloadAll();
|
||||
}
|
||||
|
||||
public void unloadAll() {
|
||||
this.loadedLocales.clear();
|
||||
}
|
||||
|
||||
protected @Nullable File determineAvailableLocaleVariation(String locale) {
|
||||
File localeFile = new File(this.localesDirectory, locale + ".lang");
|
||||
if (localeFile.exists()) {
|
||||
return localeFile;
|
||||
}
|
||||
|
||||
File[] availableLocales = this.localesDirectory.listFiles();
|
||||
if (availableLocales == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File availableLocale : availableLocales) {
|
||||
if (availableLocale.getName().startsWith(locale)) {
|
||||
return availableLocale;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected @Nullable YamlConfiguration loadFallbackLocale() throws IOException {
|
||||
URL fallbackLocaleUrl = this.plugin.getClass().getResource("/en_US.lang");
|
||||
if (fallbackLocaleUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
YamlConfiguration locale = new YamlConfiguration();
|
||||
try (Reader reader = new InputStreamReader(fallbackLocaleUrl.openStream(), StandardCharsets.UTF_8)) {
|
||||
locale.load(reader);
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
protected SongodaYamlConfig parseLocaleFile(File file) throws IOException {
|
||||
SongodaYamlConfig locale = new SongodaYamlConfig(file, this.plugin.getLogger());
|
||||
locale.load();
|
||||
|
||||
return locale;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||
import be.seeseemelk.mockbukkit.MockPlugin;
|
||||
import com.songoda.core.http.MockHttpClient;
|
||||
import com.songoda.core.http.MockHttpResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
class LocaleFileManagerTest {
|
||||
private final byte[] validIndexFile = ("# This is a comment\n\nen_US.lang\nen.yml\nde.txt\n").getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
MockBukkit.unmock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadMissingTranslations_EmptyTargetDir() throws IOException {
|
||||
MockBukkit.mock();
|
||||
MockPlugin plugin = MockBukkit.createMockPlugin();
|
||||
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> downloadedFiles = localeFileManager.downloadMissingTranslations(plugin.getDataFolder());
|
||||
|
||||
Assertions.assertSame(3, downloadedFiles.size());
|
||||
Assertions.assertEquals("en.yml", downloadedFiles.get(1));
|
||||
Assertions.assertEquals("en_US.lang", downloadedFiles.get(0));
|
||||
Assertions.assertEquals("de.txt", downloadedFiles.get(2));
|
||||
|
||||
String[] localeFiles = plugin.getDataFolder().list();
|
||||
Assertions.assertNotNull(localeFiles);
|
||||
Assertions.assertArrayEquals(new String[] {"en.yml", "en_US.lang", "de.txt"}, localeFiles);
|
||||
|
||||
Assertions.assertSame(4, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/_index.txt"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(1).contains("/test/en_US.lang"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(2).contains("/test/en.yml"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(3).contains("/test/de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadMissingTranslations() throws IOException {
|
||||
MockBukkit.mock();
|
||||
MockPlugin plugin = MockBukkit.createMockPlugin();
|
||||
|
||||
Files.createDirectories(plugin.getDataFolder().toPath());
|
||||
Files.createFile(new File(plugin.getDataFolder(), "en_US.lang").toPath());
|
||||
Files.createFile(new File(plugin.getDataFolder(), "fr.lang").toPath());
|
||||
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> downloadedFiles = localeFileManager.downloadMissingTranslations(plugin.getDataFolder());
|
||||
|
||||
Assertions.assertSame(2, downloadedFiles.size());
|
||||
Assertions.assertEquals("en.yml", downloadedFiles.get(0));
|
||||
Assertions.assertEquals("de.txt", downloadedFiles.get(1));
|
||||
|
||||
String[] localeFiles = plugin.getDataFolder().list();
|
||||
|
||||
Assertions.assertNotNull(localeFiles);
|
||||
Assertions.assertArrayEquals(new String[] {"en.yml", "en_US.lang", "fr.lang", "de.txt"}, localeFiles);
|
||||
|
||||
Assertions.assertSame(3, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/_index.txt"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(1).contains("/test/en.yml"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(2).contains("/test/de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertSame(1, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/"));
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertSame(3, availableLanguages.size());
|
||||
Assertions.assertTrue(availableLanguages.contains("en_US.lang"));
|
||||
Assertions.assertTrue(availableLanguages.contains("en.yml"));
|
||||
Assertions.assertTrue(availableLanguages.contains("de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_SpecialCharsInProjectName() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test project (special)");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertSame(1, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test+project+%28special%29/"));
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertSame(3, availableLanguages.size());
|
||||
Assertions.assertTrue(availableLanguages.contains("en_US.lang"));
|
||||
Assertions.assertTrue(availableLanguages.contains("en.yml"));
|
||||
Assertions.assertTrue(availableLanguages.contains("de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_EmptyIndex() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "empty-project");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertTrue(availableLanguages.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_UnknownProject() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(404, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "unknown");
|
||||
|
||||
Assertions.assertNull(localeFileManager.fetchAvailableLanguageFiles());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_HttpStatus500() {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(500, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
Assertions.assertThrows(IOException.class, localeFileManager::fetchAvailableLanguageFiles);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user