mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d013497c48 | ||
|
|
8691eec42b | ||
|
|
0f9d572677 | ||
|
|
baee23b6d4 | ||
|
|
532ffb1202 | ||
|
|
2a704d5eb4 | ||
|
|
e8f8466d9a | ||
|
|
9297562c99 | ||
|
|
7d62fc78de | ||
|
|
8e7e7de358 | ||
|
|
10c60d7492 | ||
|
|
aa03bd119a | ||
|
|
7bfd0ed341 |
@@ -0,0 +1,29 @@
|
|||||||
|
# .github/release.yml
|
||||||
|
# see https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
authors:
|
||||||
|
- cryptobot
|
||||||
|
- dependabot
|
||||||
|
- github-actions
|
||||||
|
categories:
|
||||||
|
- title: What's New 🎉
|
||||||
|
labels:
|
||||||
|
- type:feature-request
|
||||||
|
- type:enhancement
|
||||||
|
- title: Bugfixes 🐛
|
||||||
|
labels:
|
||||||
|
- type:security-issue
|
||||||
|
- type:bug
|
||||||
|
- type:minor-bug
|
||||||
|
- title: Other Changes 📎
|
||||||
|
labels:
|
||||||
|
- "*"
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- type:feature-request
|
||||||
|
- type:enhancement
|
||||||
|
- type:security-issue
|
||||||
|
- type:bug
|
||||||
|
- type:minor-bug
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
|
|||||||
@@ -53,4 +53,8 @@ jobs:
|
|||||||
body: |-
|
body: |-
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
Please be patient, the builds are still running. We will publish new versions of Cryptomator here in a few moments.
|
||||||
|
|
||||||
|
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
|
||||||
---
|
---
|
||||||
|
<!-- Don't forget to include the 💾 SHA-256 checksums of release artifacts: -->
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "${JAVA_HOME}/jmods"
|
--module-path "${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
--verbose
|
--verbose
|
||||||
--output runtime
|
--output runtime
|
||||||
--module-path "jfxjmods;${JAVA_HOME}/jmods"
|
--module-path "jfxjmods;${JAVA_HOME}/jmods"
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs
|
||||||
--strip-native-commands
|
--strip-native-commands
|
||||||
--no-header-files
|
--no-header-files
|
||||||
--no-man-pages
|
--no-man-pages
|
||||||
|
|||||||
Vendored
+1
-1
@@ -24,7 +24,7 @@ ${JAVA_HOME}/bin/jlink \
|
|||||||
--verbose \
|
--verbose \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
|
|||||||
Vendored
+1
-1
@@ -27,7 +27,7 @@ override_dh_auto_build:
|
|||||||
$(JAVA_HOME)/bin/jlink \
|
$(JAVA_HOME)/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JMODS_PATH}" \
|
--module-path "${JMODS_PATH}" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
|
|||||||
Vendored
+1
-1
@@ -46,7 +46,7 @@ cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
|||||||
${JAVA_HOME}/bin/jlink \
|
${JAVA_HOME}/bin/jlink \
|
||||||
--output runtime \
|
--output runtime \
|
||||||
--module-path "${JAVA_HOME}/jmods" \
|
--module-path "${JAVA_HOME}/jmods" \
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs \
|
||||||
--strip-native-commands \
|
--strip-native-commands \
|
||||||
--no-header-files \
|
--no-header-files \
|
||||||
--no-man-pages \
|
--no-man-pages \
|
||||||
|
|||||||
Vendored
+1
-1
@@ -51,7 +51,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
|
|||||||
--verbose `
|
--verbose `
|
||||||
--output runtime `
|
--output runtime `
|
||||||
--module-path "$Env:JAVA_HOME/jmods" `
|
--module-path "$Env:JAVA_HOME/jmods" `
|
||||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr `
|
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,jdk.zipfs `
|
||||||
--strip-native-commands `
|
--strip-native-commands `
|
||||||
--no-header-files `
|
--no-header-files `
|
||||||
--no-man-pages `
|
--no-man-pages `
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptomator</artifactId>
|
<artifactId>cryptomator</artifactId>
|
||||||
<version>1.8.0</version>
|
<version>1.9.0-SNAPSHOT</version>
|
||||||
<name>Cryptomator Desktop App</name>
|
<name>Cryptomator Desktop App</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
|
|||||||
@@ -1,39 +1,95 @@
|
|||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SupportedLanguages {
|
public class SupportedLanguages {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SupportedLanguages.class);
|
||||||
// these are BCP 47 language codes, not ISO. Note the "-" instead of the "_":
|
public static final String ENGLISH = "en";
|
||||||
public static final List<String> LANGUAGAE_TAGS = List.of("en", "ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fil", "fa", "fr", "gl", "he", //
|
|
||||||
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "si", "sk", "sr", "sr-Latn", "sv", "sw", //
|
|
||||||
"ta", "te", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
|
|
||||||
|
|
||||||
@Nullable
|
private final List<String> sortedLanguageTags;
|
||||||
private final String preferredLanguage;
|
private final Locale preferredLocale;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SupportedLanguages(Settings settings) {
|
public SupportedLanguages(Settings settings) {
|
||||||
this.preferredLanguage = settings.languageProperty().get();
|
var preferredLanguage = settings.languageProperty().get();
|
||||||
|
preferredLocale = preferredLanguage == null ? Locale.getDefault() : Locale.forLanguageTag(preferredLanguage);
|
||||||
|
var collator = Collator.getInstance(preferredLocale);
|
||||||
|
collator.setStrength(Collator.PRIMARY);
|
||||||
|
List<String> sortedTags = new ArrayList<>();
|
||||||
|
sortedTags.add(0, Settings.DEFAULT_LANGUAGE);
|
||||||
|
sortedTags.add(1, ENGLISH);
|
||||||
|
getSupportedLanguageTags().stream() //
|
||||||
|
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //
|
||||||
|
.forEach(sortedTags::add);
|
||||||
|
sortedLanguageTags = Collections.unmodifiableList(sortedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyPreferred() {
|
public void applyPreferred() {
|
||||||
if (preferredLanguage == null) {
|
LOG.debug("Using locale {}", preferredLocale);
|
||||||
LOG.debug("Using system locale");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var preferredLocale = Locale.forLanguageTag(preferredLanguage);
|
|
||||||
LOG.debug("Applying preferred locale {}", preferredLocale.getDisplayName(Locale.ENGLISH));
|
|
||||||
Locale.setDefault(preferredLocale);
|
Locale.setDefault(preferredLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getLanguageTags() {
|
||||||
|
return sortedLanguageTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over the /i18n directory and extracts from every localization file the BCP 47 code.
|
||||||
|
*
|
||||||
|
* @return list of supported BCP 47 language codes
|
||||||
|
*/
|
||||||
|
private static List<String> getSupportedLanguageTags() {
|
||||||
|
try {
|
||||||
|
var i18DirURI = SupportedLanguages.class.getResource("/i18n").toURI();
|
||||||
|
var uriScheme = i18DirURI.getScheme();
|
||||||
|
if (uriScheme.equals("jar")) {
|
||||||
|
final String[] array = i18DirURI.toString().split("!");
|
||||||
|
try (var jarFs = FileSystems.newFileSystem(URI.create(array[0]), Map.<String, String>of())) {
|
||||||
|
return streamDirectory(jarFs.getPath(array[1]));
|
||||||
|
}
|
||||||
|
} else if (uriScheme.equals("file")) {
|
||||||
|
return streamDirectory(Path.of(i18DirURI));
|
||||||
|
} else {
|
||||||
|
throw new IOException("Unsupported uri scheme: " + uriScheme);
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException | IOException e) {
|
||||||
|
LOG.warn("Unable to determine additional supported languages.", e);
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> streamDirectory(Path i18Dir) throws IOException {
|
||||||
|
try (var dirStream = Files.newDirectoryStream(i18Dir, "strings_*.properties")) {
|
||||||
|
return StreamSupport.stream(dirStream.spliterator(), false) //
|
||||||
|
.map(SupportedLanguages::getBCP47CodeFromFileName) //
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getBCP47CodeFromFileName(Path p) {
|
||||||
|
var fileName = p.getFileName().toString();
|
||||||
|
return fileName.substring("strings_".length(), fileName.indexOf(".properties")).replace('_', '-');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import org.cryptomator.common.LicenseHolder;
|
import org.cryptomator.common.LicenseHolder;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
@@ -35,6 +34,7 @@ public class InterfacePreferencesController implements FxController {
|
|||||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final SupportedLanguages supportedLanguages;
|
||||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||||
public CheckBox showMinimizeButtonCheckbox;
|
public CheckBox showMinimizeButtonCheckbox;
|
||||||
public CheckBox showTrayIconCheckbox;
|
public CheckBox showTrayIconCheckbox;
|
||||||
@@ -44,13 +44,14 @@ public class InterfacePreferencesController implements FxController {
|
|||||||
public RadioButton nodeOrientationRtl;
|
public RadioButton nodeOrientationRtl;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
InterfacePreferencesController(Settings settings, TrayMenuComponent trayMenu, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle) {
|
InterfacePreferencesController(Settings settings, SupportedLanguages supportedLanguages, TrayMenuComponent trayMenu, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.trayMenuInitialized = trayMenu.isInitialized();
|
this.trayMenuInitialized = trayMenu.isInitialized();
|
||||||
this.trayMenuSupported = trayMenu.isSupported();
|
this.trayMenuSupported = trayMenu.isSupported();
|
||||||
this.selectedTabProperty = selectedTabProperty;
|
this.selectedTabProperty = selectedTabProperty;
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.supportedLanguages = supportedLanguages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -66,8 +67,7 @@ public class InterfacePreferencesController implements FxController {
|
|||||||
|
|
||||||
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
|
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon());
|
||||||
|
|
||||||
preferredLanguageChoiceBox.getItems().add(null);
|
preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags());
|
||||||
preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS);
|
|
||||||
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty());
|
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty());
|
||||||
preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
|
preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle));
|
||||||
|
|
||||||
@@ -141,9 +141,7 @@ public class InterfacePreferencesController implements FxController {
|
|||||||
return resourceBundle.getString("preferences.interface.language.auto");
|
return resourceBundle.getString("preferences.interface.language.auto");
|
||||||
} else {
|
} else {
|
||||||
var locale = Locale.forLanguageTag(tag);
|
var locale = Locale.forLanguageTag(tag);
|
||||||
var lang = locale.getDisplayLanguage(locale);
|
return locale.getDisplayName();
|
||||||
var region = locale.getDisplayCountry(locale);
|
|
||||||
return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.cryptomator.launcher;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class SupportedLanguagesTest {
|
|
||||||
|
|
||||||
@DisplayName("test if resource bundle is localized")
|
|
||||||
@ParameterizedTest(name = "{0}")
|
|
||||||
@MethodSource("languageTags")
|
|
||||||
public void testResourceBundleExists(String tag) {
|
|
||||||
var locale = Locale.forLanguageTag(tag);
|
|
||||||
Assertions.assertNotEquals("und", locale.toLanguageTag(), "Undefined language tag");
|
|
||||||
|
|
||||||
var bundle = Assertions.assertDoesNotThrow(() -> ResourceBundle.getBundle("i18n.strings", locale));
|
|
||||||
|
|
||||||
Assertions.assertEquals(locale, bundle.getLocale());
|
|
||||||
Assertions.assertFalse(bundle.keySet().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Stream<String> languageTags() {
|
|
||||||
return SupportedLanguages.LANGUAGAE_TAGS.stream() //
|
|
||||||
.filter(tag -> !"en".equals(tag)); // english uses the default bundle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user