diff --git a/tools/i18n-plugin/pom.xml b/tools/i18n-plugin/pom.xml index 8057f6d4df1..43cc1a1e9e2 100644 --- a/tools/i18n-plugin/pom.xml +++ b/tools/i18n-plugin/pom.xml @@ -68,6 +68,20 @@ org.openhab.core.thing.xml ${project.version} + + org.openhab.core.bom + org.openhab.core.bom.test + ${project.version} + pom + test + + + org.openhab.core.bom + org.openhab.core.bom.test-index + ${project.version} + pom + test + diff --git a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/AbstractI18nMojo.java b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/AbstractI18nMojo.java index 5b9a29c4c43..b902dd48dad 100644 --- a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/AbstractI18nMojo.java +++ b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/AbstractI18nMojo.java @@ -43,4 +43,8 @@ protected void readAddonInfo() throws IOException { BundleInfoReader bundleInfoReader = new BundleInfoReader(getLog()); bundleInfo = bundleInfoReader.readBundleInfo(ohinfDirectory.toPath()); } + + void setOhinfDirectory(File ohinfDirectory) { + this.ohinfDirectory = ohinfDirectory; + } } diff --git a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojo.java b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojo.java index c8a24ac4f7f..0ddb25b789f 100644 --- a/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojo.java +++ b/tools/i18n-plugin/src/main/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojo.java @@ -128,4 +128,12 @@ private void writeDefaultTranslations(String translationsString) throws MojoFail throw new MojoFailureException("Failed to write generated translations to: " + translationsPath, e); } } + + void setTargetDirectory(File targetDirectory) { + this.targetDirectory = targetDirectory; + } + + void setGenerationMode(DefaultTranslationsGenerationMode generationMode) { + this.generationMode = generationMode; + } } diff --git a/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/BundleInfoReaderTest.java b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/BundleInfoReaderTest.java new file mode 100644 index 00000000000..2bfe9da66d7 --- /dev/null +++ b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/BundleInfoReaderTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.i18n.plugin; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.binding.xml.internal.BindingInfoXmlResult; + +/** + * Tests {@link BundleInfoReader}. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public class BundleInfoReaderTest { + + @Test + public void readBindingInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmeweather.bundle/OH-INF")); + + BindingInfoXmlResult bindingInfoXml = bundleInfo.getBindingInfoXml(); + assertThat(bindingInfoXml, is(notNullValue())); + if (bindingInfoXml != null) { + assertThat(bindingInfoXml.getBindingInfo().getName(), is("ACME Weather Binding")); + assertThat(bindingInfoXml.getBindingInfo().getDescription(), + is("ACME Weather - Current weather and forecasts in your city.")); + } + + assertThat(bundleInfo.getBindingId(), is("acmeweather")); + assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(1)); + assertThat(bundleInfo.getChannelTypesXml().size(), is(2)); + assertThat(bundleInfo.getConfigDescriptions().size(), is(1)); + assertThat(bundleInfo.getThingTypesXml().size(), is(2)); + } + + @Test + public void readGenericBundleInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmetts.bundle/OH-INF")); + + assertThat(bundleInfo.getBindingInfoXml(), is(nullValue())); + assertThat(bundleInfo.getBindingId(), is("")); + assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(0)); + assertThat(bundleInfo.getChannelTypesXml().size(), is(0)); + assertThat(bundleInfo.getConfigDescriptions().size(), is(1)); + assertThat(bundleInfo.getThingTypesXml().size(), is(0)); + } + + @Test + public void readPathWithoutAnyInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/infoless.bundle/OH-INF")); + + assertThat(bundleInfo.getBindingInfoXml(), is(nullValue())); + assertThat(bundleInfo.getBindingId(), is("")); + assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(0)); + assertThat(bundleInfo.getChannelTypesXml().size(), is(0)); + assertThat(bundleInfo.getConfigDescriptions().size(), is(0)); + assertThat(bundleInfo.getThingTypesXml().size(), is(0)); + } +} diff --git a/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojoTest.java b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojoTest.java new file mode 100644 index 00000000000..a99f0da8d39 --- /dev/null +++ b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/GenerateDefaultTranslationsMojoTest.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.i18n.plugin; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openhab.core.tools.i18n.plugin.DefaultTranslationsGenerationMode.*; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests {@link GenerateDefaultTranslationsMojo}. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public class GenerateDefaultTranslationsMojoTest { + + @TempDir + public @NonNullByDefault({}) Path tempPath; + public @NonNullByDefault({}) Path tempI18nPath; + + public static final Path TTS_RESOURCES_PATH = Path.of("src/test/resources/acmetts.bundle"); + public static final Path TTS_I18N_RESOURCES_PATH = TTS_RESOURCES_PATH.resolve("OH-INF/i18n"); + public static final Path TTS_ALL_PROPERTIES_PATH = TTS_I18N_RESOURCES_PATH.resolve("acmetts.properties"); + public static final Path TTS_ALL_DE_PROPERTIES_PATH = TTS_I18N_RESOURCES_PATH.resolve("acmetts_de.properties"); + public static final Path TTS_GENERATED_PROPERTIES_PATH = TTS_I18N_RESOURCES_PATH + .resolve("acmetts.generated.properties"); + public static final Path TTS_PARTIAL_PROPERTIES_PATH = TTS_I18N_RESOURCES_PATH + .resolve("acmetts.partial.properties"); + + public static final Path WEATHER_RESOURCES_PATH = Path.of("src/test/resources/acmeweather.bundle"); + public static final Path WEATHER_I18N_RESOURCES_PATH = WEATHER_RESOURCES_PATH.resolve("OH-INF/i18n"); + public static final Path WEATHER_ALL_PROPERTIES_PATH = WEATHER_I18N_RESOURCES_PATH + .resolve("acmeweather.properties"); + public static final Path WEATHER_ALL_DE_PROPERTIES_PATH = WEATHER_I18N_RESOURCES_PATH + .resolve("acmeweather_de.properties"); + public static final Path WEATHER_GENERATED_PROPERTIES_PATH = WEATHER_I18N_RESOURCES_PATH + .resolve("acmeweather.generated.properties"); + public static final Path WEATHER_PARTIAL_PROPERTIES_PATH = WEATHER_I18N_RESOURCES_PATH + .resolve("acmeweather.partial.properties"); + + public static final Path INFOLESS_RESOURCES_PATH = Path.of("src/test/resources/infoless.bundle"); + + private @NonNullByDefault({}) GenerateDefaultTranslationsMojo mojo; + + private void copyPath(Path src, Path dest) throws IOException { + try (Stream stream = Files.walk(src)) { + stream.forEach(source -> copy(source, dest.resolve(src.relativize(source)))); + } + } + + private void copy(Path source, Path dest) { + try { + Files.copy(source, dest, REPLACE_EXISTING); + } catch (IOException e) { + throw new UncheckedIOException(e.getMessage(), e); + } + } + + private void deleteTempI18nPath() throws IOException { + try (DirectoryStream entries = Files.newDirectoryStream(tempI18nPath)) { + for (Path entry : entries) { + Files.delete(entry); + } + } + + Files.delete(tempI18nPath); + } + + @BeforeEach + public void before() { + tempI18nPath = tempPath.resolve("OH-INF/i18n"); + + mojo = new GenerateDefaultTranslationsMojo(); + mojo.setLog(new SystemStreamLog()); + mojo.setOhinfDirectory(tempPath.resolve("OH-INF").toFile()); + mojo.setTargetDirectory(tempI18nPath.toFile()); + } + + private void assertSameProperties(Path expectedPath, Path actualPath) throws IOException { + String expected = Files.readString(expectedPath); + String actual = Files.readString(actualPath); + assertThat(expected, equalTo(actual)); + } + + @Test + public void addMissingBindingTranslationsWithoutI18nPath() throws IOException, MojoFailureException { + copyPath(WEATHER_RESOURCES_PATH, tempPath); + deleteTempI18nPath(); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(WEATHER_GENERATED_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather.properties")); + } + + @Test + public void addMissingBindingTranslationsNoChanges() throws IOException, MojoFailureException { + copyPath(WEATHER_RESOURCES_PATH, tempPath); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(WEATHER_ALL_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather.properties")); + assertSameProperties(WEATHER_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather_de.properties")); + } + + @Test + public void addMissingBindingTranslationsToPartialTranslation() throws IOException, MojoFailureException { + copyPath(WEATHER_RESOURCES_PATH, tempPath); + Files.move(tempI18nPath.resolve("acmeweather.partial.properties"), + tempI18nPath.resolve("acmeweather.properties"), REPLACE_EXISTING); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(WEATHER_ALL_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather.properties")); + assertSameProperties(WEATHER_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather_de.properties")); + } + + @Test + public void skipTranslationsBindingTranslationsGeneratingWithExistingTranslations() + throws IOException, MojoFailureException { + copyPath(WEATHER_RESOURCES_PATH, tempPath); + Files.move(tempI18nPath.resolve("acmeweather.partial.properties"), + tempI18nPath.resolve("acmeweather.properties"), REPLACE_EXISTING); + + mojo.setGenerationMode(ADD_MISSING_FILES); + mojo.execute(); + + assertSameProperties(WEATHER_PARTIAL_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather.properties")); + assertSameProperties(WEATHER_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather_de.properties")); + } + + @Test + public void regenerateBindingTranslations() throws IOException, MojoFailureException { + copyPath(WEATHER_RESOURCES_PATH, tempPath); + + mojo.setGenerationMode(REGENERATE_FILES); + mojo.execute(); + + assertSameProperties(WEATHER_GENERATED_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather.properties")); + assertSameProperties(WEATHER_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmeweather_de.properties")); + } + + @Test + public void addMissingGenericBundleTranslationsWithoutI18nPath() throws IOException, MojoFailureException { + copyPath(TTS_RESOURCES_PATH, tempPath); + deleteTempI18nPath(); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(TTS_GENERATED_PROPERTIES_PATH, tempI18nPath.resolve("acmetts.properties")); + } + + @Test + public void addMissingGenericBundleTranslationsNoChanges() throws IOException, MojoFailureException { + copyPath(TTS_RESOURCES_PATH, tempPath); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(TTS_ALL_PROPERTIES_PATH, tempI18nPath.resolve("acmetts.properties")); + assertSameProperties(TTS_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmetts_de.properties")); + } + + @Test + public void addMissingGenericBundleTranslationsToPartialTranslation() throws IOException, MojoFailureException { + copyPath(TTS_RESOURCES_PATH, tempPath); + Files.move(tempI18nPath.resolve("acmetts.partial.properties"), tempI18nPath.resolve("acmetts.properties"), + REPLACE_EXISTING); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + + assertSameProperties(TTS_ALL_PROPERTIES_PATH, tempI18nPath.resolve("acmetts.properties")); + assertSameProperties(TTS_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmetts_de.properties")); + } + + @Test + public void skipTranslationsGenericBundleTranslationsGeneratingWithExistingTranslations() + throws IOException, MojoFailureException { + copyPath(TTS_RESOURCES_PATH, tempPath); + Files.move(tempI18nPath.resolve("acmetts.partial.properties"), tempI18nPath.resolve("acmetts.properties"), + REPLACE_EXISTING); + + mojo.setGenerationMode(ADD_MISSING_FILES); + mojo.execute(); + + assertSameProperties(TTS_PARTIAL_PROPERTIES_PATH, tempI18nPath.resolve("acmetts.properties")); + assertSameProperties(TTS_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmetts_de.properties")); + } + + @Test + public void regenerateGenericBundleTranslations() throws IOException, MojoFailureException { + copyPath(TTS_RESOURCES_PATH, tempPath); + + mojo.setGenerationMode(REGENERATE_FILES); + mojo.execute(); + + assertSameProperties(TTS_GENERATED_PROPERTIES_PATH, tempI18nPath.resolve("acmetts.properties")); + assertSameProperties(TTS_ALL_DE_PROPERTIES_PATH, tempI18nPath.resolve("acmetts_de.properties")); + } + + @Test + public void addMissingTranslationsWithoutOhInfPath() throws IOException, MojoFailureException { + copyPath(INFOLESS_RESOURCES_PATH, tempPath); + + mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS); + mojo.execute(); + } +} diff --git a/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/PropertiesToTranslationsConverterTest.java b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/PropertiesToTranslationsConverterTest.java new file mode 100644 index 00000000000..813902bbc2a --- /dev/null +++ b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/PropertiesToTranslationsConverterTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.i18n.plugin; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; + +import java.nio.file.Path; +import java.util.stream.Collectors; + +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertiesToTranslationsConverter}. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public class PropertiesToTranslationsConverterTest { + + @Test + public void readBindingInfo() { + PropertiesToTranslationsConverter converter = new PropertiesToTranslationsConverter(new SystemStreamLog()); + Translations translations = converter + .convert(Path.of("src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.properties")); + + assertThat(translations.hasTranslations(), is(true)); + assertThat(translations.sections.size(), is(6)); + assertThat(translations.keysStream().count(), is(31L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, containsString("# binding")); + assertThat(lines, containsString("binding.acmeweather.name = ACME Weather Binding")); + assertThat(lines, containsString( + "binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city.")); + assertThat(lines, containsString( + "channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial).")); + assertThat(lines, containsString( + "channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS")); + assertThat(lines, containsString("CUSTOM_KEY = Provides various weather data from the ACME weather service")); + } + + @Test + public void readGenericBundleInfo() { + PropertiesToTranslationsConverter converter = new PropertiesToTranslationsConverter(new SystemStreamLog()); + Translations translations = converter + .convert(Path.of("src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.properties")); + + assertThat(translations.hasTranslations(), is(true)); + assertThat(translations.sections.size(), is(2)); + assertThat(translations.keysStream().count(), is(19L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + + assertThat(lines, containsString("voice.config.acmetts.clientId.label = Client Id")); + assertThat(lines, + containsString("voice.config.acmetts.clientId.description = ACME Platform OAuth 2.0-Client Id.")); + assertThat(lines, containsString("voice.config.acmetts.pitch.label = Pitch")); + assertThat(lines, containsString( + "voice.config.acmetts.pitch.description = Customize the pitch of your selected voice, up to 20 semitones more or less than the default output.")); + } + + @Test + public void readPathWithoutAnyInfo() { + PropertiesToTranslationsConverter converter = new PropertiesToTranslationsConverter(new SystemStreamLog()); + Translations translations = converter + .convert(Path.of("src/test/resources/infoless.bundle/OH-INF/i18n/nonexisting.properties")); + + assertThat(translations.hasTranslations(), is(false)); + assertThat(translations.keysStream().count(), is(0L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, is(emptyString())); + } +} diff --git a/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/TranslationsMergerTest.java b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/TranslationsMergerTest.java new file mode 100644 index 00000000000..e477948c3a0 --- /dev/null +++ b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/TranslationsMergerTest.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.i18n.plugin; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openhab.core.tools.i18n.plugin.Translations.TranslationsEntry.entry; +import static org.openhab.core.tools.i18n.plugin.Translations.TranslationsGroup.group; +import static org.openhab.core.tools.i18n.plugin.Translations.TranslationsSection.section; +import static org.openhab.core.tools.i18n.plugin.Translations.translations; + +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link TranslationsMerger}. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public class TranslationsMergerTest { + + @Test + public void mergeEmptyTranslations() { + Translations mainTranslations = translations(); + Translations missingTranslations = translations(); + + TranslationsMerger merger = new TranslationsMerger(); + merger.merge(mainTranslations, missingTranslations); + + assertThat(mainTranslations.hasTranslations(), is(false)); + assertThat(mainTranslations.keysStream().count(), is(0L)); + + assertThat(missingTranslations.hasTranslations(), is(false)); + assertThat(missingTranslations.keysStream().count(), is(0L)); + } + + @Test + public void mergeDifferentTranslations() { + Translations mainTranslations = Translations.translations( // + section("main section 1", group( // + entry("key1", "mainValue1"), // + entry("key2", "mainValue2"))), + section("main section 2", group( // + entry("key3", "mainValue3"), // + entry("key4", "mainValue4")))); + + Translations missingTranslations = Translations.translations( // + section("missing section 1", group( // + entry("key1", "missingValue1"), // + entry("key2", "missingValue2"))), + section("missing section 3", group( // + entry("key5", "missingValue5"), // + entry("key6", "missingValue6")))); + + TranslationsMerger merger = new TranslationsMerger(); + merger.merge(mainTranslations, missingTranslations); + + assertThat(mainTranslations.hasTranslations(), is(true)); + assertThat(mainTranslations.keysStream().count(), is(6L)); + assertThat(mainTranslations.sections.size(), is(3)); + + String lines = mainTranslations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, containsString("# main section 1")); + assertThat(lines, containsString("key1 = mainValue1")); + assertThat(lines, containsString("key2 = mainValue2")); + assertThat(lines, containsString("# main section 2")); + assertThat(lines, containsString("key3 = mainValue3")); + assertThat(lines, containsString("key4 = mainValue4")); + assertThat(lines, containsString("# missing section 3")); + assertThat(lines, containsString("key5 = missingValue5")); + assertThat(lines, containsString("key6 = missingValue6")); + } +} diff --git a/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/XmlToTranslationsConverterTest.java b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/XmlToTranslationsConverterTest.java new file mode 100644 index 00000000000..f8e39bfd7df --- /dev/null +++ b/tools/i18n-plugin/src/test/java/org/openhab/core/tools/i18n/plugin/XmlToTranslationsConverterTest.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.i18n.plugin; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.stream.Collectors; + +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link XmlToTranslationsConverter}. + * + * @author Wouter Born - Initial contribution + */ +@NonNullByDefault +public class XmlToTranslationsConverterTest { + + @Test + public void convertBindingInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmeweather.bundle/OH-INF")); + + XmlToTranslationsConverter converter = new XmlToTranslationsConverter(); + Translations translations = converter.convert(bundleInfo); + + assertThat(translations.hasTranslations(), is(true)); + assertThat(translations.sections.size(), is(7)); + assertThat(translations.keysStream().count(), is(30L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, containsString("# binding")); + assertThat(lines, containsString("binding.acmeweather.name = ACME Weather Binding")); + assertThat(lines, containsString( + "binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city.")); + assertThat(lines, containsString( + "channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial).")); + assertThat(lines, containsString( + "channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS")); + } + + @Test + public void convertGenericBundleInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmetts.bundle/OH-INF")); + + XmlToTranslationsConverter converter = new XmlToTranslationsConverter(); + Translations translations = converter.convert(bundleInfo); + + assertThat(translations.hasTranslations(), is(true)); + assertThat(translations.sections.size(), is(1)); + assertThat(translations.keysStream().count(), is(18L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, containsString("voice.config.acmetts.clientId.label = Client Id")); + assertThat(lines, + containsString("voice.config.acmetts.clientId.description = ACME Platform OAuth 2.0-Client Id.")); + assertThat(lines, containsString("voice.config.acmetts.pitch.label = Pitch")); + assertThat(lines, containsString( + "voice.config.acmetts.pitch.description = Customize the pitch of your selected voice, up to 20 semitones more or less than the default output.")); + } + + @Test + public void convertPathWithoutAnyInfo() throws IOException { + BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog()); + BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/infoless.bundle/OH-INF")); + + XmlToTranslationsConverter converter = new XmlToTranslationsConverter(); + Translations translations = converter.convert(bundleInfo); + + assertThat(translations.hasTranslations(), is(false)); + assertThat(translations.keysStream().count(), is(0L)); + + String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator())); + assertThat(lines, is(emptyString())); + } +} diff --git a/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/config/config.xml b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/config/config.xml new file mode 100644 index 00000000000..4bf93ea2268 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/config/config.xml @@ -0,0 +1,55 @@ + + + + + + + Authentication for connecting to ACME Platform. + + + + Parameters for ACME TTS API. + + + + + ACME Platform OAuth 2.0-Client Id. + + + password + + ACME Platform OAuth 2.0-Client Secret. + + + + Please go to your browser ... https://accounts.google.com/o/oauth2/auth?client_id={{clientId}}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/cloud-platform&response_type=code ... to generate an auth-code and paste it here.]]> + + + + Customize the pitch of your selected voice, up to 20 semitones more or less than the default output. + 0 + + + + Increase the volume of the output by up to 16db or decrease the volume up to -96db. + 0 + + + + Speaking rate can be 4x faster or slower than the normal rate. + 1 + + + true + + Purges the cache e.g. after testing different voice configuration parameters. When enabled the cache is + purged once. Make sure to disable this setting again so the cache is maintained after restarts. + false + + + + diff --git a/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.generated.properties b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.generated.properties new file mode 100644 index 00000000000..9b6367b2c01 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.generated.properties @@ -0,0 +1,18 @@ +voice.config.acmetts.authcode.label = Authorization Code +voice.config.acmetts.authcode.description = The auth-code is a one-time code needed to retrieve the necessary access-codes from ACME Platform. Please go to your browser ... https://accounts.google.com/o/oauth2/auth?client_id={{clientId}}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/cloud-platform&response_type=code ... to generate an auth-code and paste it here. +voice.config.acmetts.clientId.label = Client Id +voice.config.acmetts.clientId.description = ACME Platform OAuth 2.0-Client Id. +voice.config.acmetts.clientSecret.label = Client Secret +voice.config.acmetts.clientSecret.description = ACME Platform OAuth 2.0-Client Secret. +voice.config.acmetts.group.authentication.label = Authentication +voice.config.acmetts.group.authentication.description = Authentication for connecting to ACME Platform. +voice.config.acmetts.group.tts.label = TTS Configuration +voice.config.acmetts.group.tts.description = Parameters for ACME TTS API. +voice.config.acmetts.pitch.label = Pitch +voice.config.acmetts.pitch.description = Customize the pitch of your selected voice, up to 20 semitones more or less than the default output. +voice.config.acmetts.purgeCache.label = Purge Cache +voice.config.acmetts.purgeCache.description = Purges the cache e.g. after testing different voice configuration parameters. When enabled the cache is purged once. Make sure to disable this setting again so the cache is maintained after restarts. +voice.config.acmetts.speakingRate.label = Speaking Rate +voice.config.acmetts.speakingRate.description = Speaking rate can be 4x faster or slower than the normal rate. +voice.config.acmetts.volumeGain.label = Volume Gain +voice.config.acmetts.volumeGain.description = Increase the volume of the output by up to 16db or decrease the volume up to -96db. diff --git a/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.partial.properties b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.partial.properties new file mode 100644 index 00000000000..2afe16d3f62 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.partial.properties @@ -0,0 +1,3 @@ +# custom + +CUSTOM_KEY = Could not connect to the ACME TTS API diff --git a/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.properties b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.properties new file mode 100644 index 00000000000..ffb017cc929 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts.properties @@ -0,0 +1,22 @@ +voice.config.acmetts.authcode.label = Authorization Code +voice.config.acmetts.authcode.description = The auth-code is a one-time code needed to retrieve the necessary access-codes from ACME Platform. Please go to your browser ... https://accounts.google.com/o/oauth2/auth?client_id={{clientId}}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/cloud-platform&response_type=code ... to generate an auth-code and paste it here. +voice.config.acmetts.clientId.label = Client Id +voice.config.acmetts.clientId.description = ACME Platform OAuth 2.0-Client Id. +voice.config.acmetts.clientSecret.label = Client Secret +voice.config.acmetts.clientSecret.description = ACME Platform OAuth 2.0-Client Secret. +voice.config.acmetts.group.authentication.label = Authentication +voice.config.acmetts.group.authentication.description = Authentication for connecting to ACME Platform. +voice.config.acmetts.group.tts.label = TTS Configuration +voice.config.acmetts.group.tts.description = Parameters for ACME TTS API. +voice.config.acmetts.pitch.label = Pitch +voice.config.acmetts.pitch.description = Customize the pitch of your selected voice, up to 20 semitones more or less than the default output. +voice.config.acmetts.purgeCache.label = Purge Cache +voice.config.acmetts.purgeCache.description = Purges the cache e.g. after testing different voice configuration parameters. When enabled the cache is purged once. Make sure to disable this setting again so the cache is maintained after restarts. +voice.config.acmetts.speakingRate.label = Speaking Rate +voice.config.acmetts.speakingRate.description = Speaking rate can be 4x faster or slower than the normal rate. +voice.config.acmetts.volumeGain.label = Volume Gain +voice.config.acmetts.volumeGain.description = Increase the volume of the output by up to 16db or decrease the volume up to -96db. + +# custom + +CUSTOM_KEY = Could not connect to the ACME TTS API diff --git a/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts_de.properties b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts_de.properties new file mode 100644 index 00000000000..bf92ddba041 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmetts.bundle/OH-INF/i18n/acmetts_de.properties @@ -0,0 +1,22 @@ +voice.config.acmetts.authcode.label = Autorisierungscode +voice.config.acmetts.authcode.description = Der Autorisierungscode ist ein einmaliger Code, der benötigt wird, um den notwendigen Authentifizierungscode von der ACME Plattform abzurufen. Aufruf der URL ... https\://accounts.google.com/o/oauth2/auth?client_id\={{clientId}}&redirect_uri\=urn\:ietf\:wg\:oauth\:2.0\:oob&scope\=https\://www.googleapis.com/auth/cloud-platform&response_type\=code im Browser, um einen Autorisierungscode zu generieren und ihn hier einzufügen. +voice.config.acmetts.clientId.label = Client Id +voice.config.acmetts.clientId.description = ACME Plattform OAuth 2.0-Client Id. +voice.config.acmetts.clientSecret.label = Client Secret +voice.config.acmetts.clientSecret.description = ACME Plattform OAuth 2.0-Client Secret. +voice.config.acmetts.group.authentication.label = Authentifizierungscode +voice.config.acmetts.group.authentication.description = Authentifizierungscode für die Verbindung zur ACME Plattform. +voice.config.acmetts.group.tts.label = Sprachkonfiguration +voice.config.acmetts.group.tts.description = Sprachkonfiguration für die ACME TTS API. +voice.config.acmetts.pitch.label = Tonhöhe +voice.config.acmetts.pitch.description = Die Tonhöhe der gewählten Stimme kann bis zu 20 Halbtöne höher oder niedriger sein als der Standardwert. +voice.config.acmetts.purgeCache.label = Cache Leeren +voice.config.acmetts.purgeCache.description = Leert den Cache z.B. nach dem Testen verschiedener Sprachkonfigurationen. Wenn aktiviert, wird der Cache einmalig gelöscht. Stellen Sie sicher, dass Sie diese Einstellung im Anschluss deaktivieren wird, so dass der Cache nach einem Neustart genutzt wird. +voice.config.acmetts.speakingRate.label = Sprachgeschwindigkeit +voice.config.acmetts.speakingRate.description = Die Sprachgeschwindigkeit kann bis zu 4x schneller oder langsamer sein als die normale Geschwindigkeit. +voice.config.acmetts.volumeGain.label = Lautstärke Verstärkung +voice.config.acmetts.volumeGain.description = Die Lautstärke der Sprachausgabe kann um bis zu 16db erhöht oder um bis zu -96db verringert werden. + +# custom + +CUSTOM_KEY = Keine Verbindung mit ACME TTS API möglich diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/binding/binding.xml b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/binding/binding.xml new file mode 100644 index 00000000000..97266803f7a --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + ACME Weather Binding + ACME Weather - Current weather and forecasts in your city. + + diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/config/config.xml b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/config/config.xml new file mode 100644 index 00000000000..f738ff332a8 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/config/config.xml @@ -0,0 +1,25 @@ + + + + + + + Specifies the refresh interval (in minutes). + 60 + + + + Language to be used by the ACME API. + + + + + + + + + + diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.generated.properties b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.generated.properties new file mode 100644 index 00000000000..bbbddaebdf3 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.generated.properties @@ -0,0 +1,44 @@ +# binding + +binding.acmeweather.name = ACME Weather Binding +binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city. + +# thing types + +thing-type.acmeweather.weather-with-group.label = Weather Information with Group +thing-type.acmeweather.weather-with-group.group.forecastToday.label = Today +thing-type.acmeweather.weather-with-group.group.forecastToday.description = This is the weather forecast for today. +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.label = Weather Forecast Tomorrow +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.description = This is the weather forecast for tomorrow. +thing-type.acmeweather.weather.label = Weather Information * +thing-type.acmeweather.weather.channel.minTemperature.label = Min. Temperature +thing-type.acmeweather.weather.channel.minTemperature.description = Minimum temperature in degrees Celsius (metric) or Fahrenheit (imperial). + +# thing types config + +thing-type.config.acmeweather.weather.language.label = Language +thing-type.config.acmeweather.weather.language.description = Language to be used by the ACME API. +thing-type.config.acmeweather.weather.language.option.nl = Dutch +thing-type.config.acmeweather.weather.language.option.de = German +thing-type.config.acmeweather.weather.language.option.en = English +thing-type.config.acmeweather.weather.language.option.fr = French +thing-type.config.acmeweather.weather.refreshInterval.label = Refresh Interval +thing-type.config.acmeweather.weather.refreshInterval.description = Specifies the refresh interval (in minutes). + +# channel group types + +channel-group-type.acmeweather.forecast.label = Weather information group +channel-group-type.acmeweather.forecast.description = Weather information group description. +channel-group-type.acmeweather.forecast.channel.maxTemperature.label = Max. Temperature +channel-group-type.acmeweather.forecast.channel.maxTemperature.description = Maximum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). +channel-group-type.acmeweather.forecast.channel.minTemperature.label = Min. Temperature +channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). + +# channel types + +channel-type.acmeweather.temperature.label = Temperature +channel-type.acmeweather.temperature.description = Temperature in degrees Celsius (metric) or Fahrenheit (imperial). +channel-type.acmeweather.temperature.state.option.VALUE = My label +channel-type.acmeweather.time-stamp.label = Observation Time +channel-type.acmeweather.time-stamp.description = Time of data observation. +channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.partial.properties b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.partial.properties new file mode 100644 index 00000000000..141c516bfe9 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.partial.properties @@ -0,0 +1,3 @@ +# custom + +CUSTOM_KEY = Provides various weather data from the ACME weather service diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.properties b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.properties new file mode 100644 index 00000000000..3dc43a53a91 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather.properties @@ -0,0 +1,48 @@ +# binding + +binding.acmeweather.name = ACME Weather Binding +binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city. + +# thing types + +thing-type.acmeweather.weather-with-group.label = Weather Information with Group +thing-type.acmeweather.weather-with-group.group.forecastToday.label = Today +thing-type.acmeweather.weather-with-group.group.forecastToday.description = This is the weather forecast for today. +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.label = Weather Forecast Tomorrow +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.description = This is the weather forecast for tomorrow. +thing-type.acmeweather.weather.label = Weather Information * +thing-type.acmeweather.weather.channel.minTemperature.label = Min. Temperature +thing-type.acmeweather.weather.channel.minTemperature.description = Minimum temperature in degrees Celsius (metric) or Fahrenheit (imperial). + +# thing types config + +thing-type.config.acmeweather.weather.language.label = Language +thing-type.config.acmeweather.weather.language.description = Language to be used by the ACME API. +thing-type.config.acmeweather.weather.language.option.nl = Dutch +thing-type.config.acmeweather.weather.language.option.de = German +thing-type.config.acmeweather.weather.language.option.en = English +thing-type.config.acmeweather.weather.language.option.fr = French +thing-type.config.acmeweather.weather.refreshInterval.label = Refresh Interval +thing-type.config.acmeweather.weather.refreshInterval.description = Specifies the refresh interval (in minutes). + +# channel group types + +channel-group-type.acmeweather.forecast.label = Weather information group +channel-group-type.acmeweather.forecast.description = Weather information group description. +channel-group-type.acmeweather.forecast.channel.maxTemperature.label = Max. Temperature +channel-group-type.acmeweather.forecast.channel.maxTemperature.description = Maximum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). +channel-group-type.acmeweather.forecast.channel.minTemperature.label = Min. Temperature +channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). + +# channel types + +channel-type.acmeweather.temperature.label = Temperature +channel-type.acmeweather.temperature.description = Temperature in degrees Celsius (metric) or Fahrenheit (imperial). +channel-type.acmeweather.temperature.state.option.VALUE = My label +channel-type.acmeweather.time-stamp.label = Observation Time +channel-type.acmeweather.time-stamp.description = Time of data observation. +channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS + +# custom + +CUSTOM_KEY = Provides various weather data from the ACME weather service diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather_de.properties b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather_de.properties new file mode 100644 index 00000000000..de7bd445cc5 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/i18n/acmeweather_de.properties @@ -0,0 +1,48 @@ +# binding + +binding.acmeweather.name = ACME Wetter Binding +binding.acmeweather.description = ACME Wetter - Aktuelles Wetter und Prognosen in Ihrer Stadt. + +# thing types + +thing-type.acmeweather.weather-with-group.label = Wetterinformationen mit Gruppe +thing-type.acmeweather.weather-with-group.group.forecastToday.label = Wettervorhersage heute +thing-type.acmeweather.weather-with-group.group.forecastToday.description = Wettervorhersage für den heutigen Tag. +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.label = Wettervorhersage morgen +thing-type.acmeweather.weather-with-group.group.forecastTomorrow.description = Wettervorhersage für den morgigen Tag. +thing-type.acmeweather.weather.label = Wetterinformation +thing-type.acmeweather.weather.channel.minTemperature.label = Min. Temperatur +thing-type.acmeweather.weather.channel.minTemperature.description = Minimale Temperatur in Grad Celsius (Metrisch) oder Fahrenheit (Imperial). + +# thing types config + +thing-type.config.acmeweather.weather.language.label = Sprache +thing-type.config.acmeweather.weather.language.description = Sprache zur Anzeige der Daten. +thing-type.config.acmeweather.weather.language.option.nl = Holländisch +thing-type.config.acmeweather.weather.language.option.de = Deutsch +thing-type.config.acmeweather.weather.language.option.en = Englisch +thing-type.config.acmeweather.weather.language.option.fr = Französisch +thing-type.config.acmeweather.weather.refreshInterval.label = Abfrageintervall +thing-type.config.acmeweather.weather.refreshInterval.description = Intervall zur Abfrage der OpenWeatherMap API (in min). + +# channel group types + +channel-group-type.acmeweather.forecast.label = Wetterinformation mit Gruppe +channel-group-type.acmeweather.forecast.description = Wetterinformation mit Gruppe Beschreibung. +channel-group-type.acmeweather.forecast.channel.maxTemperature.label = Max. Temperatur +channel-group-type.acmeweather.forecast.channel.maxTemperature.description = Maximale vorhergesagte Temperatur in Grad Celsius (Metrisch) oder Fahrenheit (Imperial). +channel-group-type.acmeweather.forecast.channel.minTemperature.label = Min. Temperatur +channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimale vorhergesagte Temperatur in Grad Celsius (Metrisch) oder Fahrenheit (Imperial). + +# channel types + +channel-type.acmeweather.temperature.label = Temperatur +channel-type.acmeweather.temperature.description = Temperatur in Grad Celsius (Metrisch) oder Fahrenheit (Imperial). +channel-type.acmeweather.temperature.state.option.VALUE = Mein String +channel-type.acmeweather.time-stamp.label = Letzte Messung +channel-type.acmeweather.time-stamp.description = Zeigt den Zeitpunkt der letzten Messung an. +channel-type.acmeweather.time-stamp.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS + +# custom + +CUSTOM_KEY = Stellt verschiedene Wetterdaten vom ACME Wetterdienst bereit diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/channel-types.xml b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/channel-types.xml new file mode 100644 index 00000000000..92bfb684b73 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/channel-types.xml @@ -0,0 +1,44 @@ + + + + + + + Weather information group description. + + + + + Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). + + + + Maximum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial). + + + + + + + Number + + Temperature in degrees Celsius (metric) or Fahrenheit (imperial). + + + + + + + + + DateTime + + Time of data observation. + Time + + + + diff --git a/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/thing-types.xml b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/thing-types.xml new file mode 100644 index 00000000000..13970984c66 --- /dev/null +++ b/tools/i18n-plugin/src/test/resources/acmeweather.bundle/OH-INF/thing/thing-types.xml @@ -0,0 +1,40 @@ + + + + + + + @text/CUSTOM_KEY + + + + + + Minimum temperature in degrees Celsius (metric) or Fahrenheit (imperial). + + + + + + + + + + + + + + + This is the weather forecast for today. + + + + This is the weather forecast for tomorrow. + + + + + diff --git a/tools/i18n-plugin/src/test/resources/infoless.bundle/.gitkeep b/tools/i18n-plugin/src/test/resources/infoless.bundle/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d