diff --git a/build.gradle b/build.gradle index 2a30313..47b0616 100644 --- a/build.gradle +++ b/build.gradle @@ -95,6 +95,7 @@ dependencies { compile "com.querydsl:querydsl-apt:$queryDslVersion:jpa" //vaadin compile 'com.vaadin:vaadin-spring-boot-starter' + compile 'org.vaadin.spring.addons:vaadin-spring-addon-i18n:2.0.0.RELEASE' compile 'com.vaadin:vaadin-push' compile('org.vaadin.addon:jfreechartwrapper:4.0.0') { exclude group: 'javax.servlet', module: 'servlet-api' @@ -138,6 +139,15 @@ task generateThemeClasses(type: com.faendir.acra.gradle.ThemeClassGenerator) { compileJava.dependsOn(generateThemeClasses) +task generateMessageClasses(type: com.faendir.acra.gradle.I18nClassGenerator) { + inputDirectory file('src/main/resources/i18n/com/faendir/acra') + outputDirectory file("$generated/faendir/java") + packageName 'com.faendir.acra.i18n' + className 'Messages' +} + +compileJava.dependsOn(generateMessageClasses) + test { testLogging { events "failed" diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 360dc1c..0995420 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -21,4 +21,5 @@ repositories { dependencies { gradleApi() compile 'com.squareup:javapoet:1.11.1' + compile 'com.google.guava:guava:26.0-jre' } \ No newline at end of file diff --git a/buildSrc/src/main/groovy/com/faendir/acra/gradle/I18nClassGenerator.groovy b/buildSrc/src/main/groovy/com/faendir/acra/gradle/I18nClassGenerator.groovy new file mode 100644 index 0000000..32f5bb8 --- /dev/null +++ b/buildSrc/src/main/groovy/com/faendir/acra/gradle/I18nClassGenerator.groovy @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.gradle + +import com.google.common.base.CaseFormat +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.TypeSpec +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.* +import org.gradle.plugins.ide.idea.IdeaPlugin + +import static javax.lang.model.element.Modifier.* +/** + * @author lukas + * @since 15.08.18 + */ +@CacheableTask +class I18nClassGenerator extends DefaultTask { + @SkipWhenEmpty + @InputDirectory + File inputDirectory + @OutputDirectory + File outputDirectory + String packageName + String className + + I18nClassGenerator() { + project.afterEvaluate { + project.sourceSets.main.java.srcDirs outputs.files + project.plugins.withType(IdeaPlugin.class, { IdeaPlugin idea -> idea.model.module.generatedSourceDirs += outputs.files }) + } + } + + @TaskAction + void exec() { + Set keys = new HashSet<>() + for (File file : project.fileTree(inputDirectory)) { + try { + Properties properties = new Properties() + properties.load(file.newReader()) + keys.addAll(properties.keys() as Collection) + } catch (ignored){ + } + } + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(PUBLIC, FINAL) + for (String key : keys) { + classBuilder.addField(FieldSpec.builder(String, CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, key), PUBLIC, STATIC, FINAL).initializer('$S', key).build()) + } + JavaFile.builder(packageName, classBuilder.build()) + .skipJavaLangImports(true) + .indent(" ") + .build() + .writeTo(outputDirectory) + } + +} diff --git a/src/main/java/com/faendir/acra/i18n/HasI18n.java b/src/main/java/com/faendir/acra/i18n/HasI18n.java new file mode 100644 index 0000000..2aa2b9f --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/HasI18n.java @@ -0,0 +1,26 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.faendir.acra.i18n; + +import org.vaadin.spring.i18n.I18N; + +/** + * @author lukas + * @since 16.08.18 + */ +public interface HasI18n { + I18N getI18n(); +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nAccordion.java b/src/main/java/com/faendir/acra/i18n/I18nAccordion.java new file mode 100644 index 0000000..f64ce61 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nAccordion.java @@ -0,0 +1,53 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.Accordion; +import com.vaadin.ui.Component; +import com.vaadin.ui.TabSheet; +import org.springframework.data.util.Pair; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * @author lukas + * @since 16.08.18 + */ +public class I18nAccordion extends Accordion implements Translatable { + private final I18N i18n; + private final Map> tabCaptionIds; + + public I18nAccordion(I18N i18n) { + this.i18n = i18n; + tabCaptionIds = new HashMap<>(); + } + + public Tab addTab(Component c, String captionId, Object... parameters) { + Tab tab = super.addTab(c, i18n.get(captionId, parameters)); + tabCaptionIds.put(tab, Pair.of(captionId, parameters)); + return tab; + } + + @Override + public void updateMessageStrings(Locale locale) { + tabCaptionIds.forEach((tab, pair) -> tab.setCaption(i18n.get(pair.getFirst(), locale, pair.getSecond()))); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nButton.java b/src/main/java/com/faendir/acra/i18n/I18nButton.java new file mode 100644 index 0000000..0e5210e --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nButton.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.Button; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nButton extends Button implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nButton(ClickListener clickListener, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + addClickListener(clickListener); + } + + public I18nButton(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nCheckBox.java b/src/main/java/com/faendir/acra/i18n/I18nCheckBox.java new file mode 100644 index 0000000..2c3a0d6 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nCheckBox.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.CheckBox; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nCheckBox extends CheckBox implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nCheckBox(boolean value, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + setValue(value); + } + + public I18nCheckBox(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nComboBox.java b/src/main/java/com/faendir/acra/i18n/I18nComboBox.java new file mode 100644 index 0000000..8b0b9f9 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nComboBox.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.faendir.acra.i18n; + +import com.vaadin.ui.ComboBox; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Collection; +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nComboBox extends ComboBox implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nComboBox(Collection values, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + setItems(values); + } + + public I18nComboBox(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nConfiguration.java b/src/main/java/com/faendir/acra/i18n/I18nConfiguration.java new file mode 100644 index 0000000..546b504 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nConfiguration.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.vaadin.spring.i18n.MessageProvider; +import org.vaadin.spring.i18n.ResourceBundleMessageProvider; +import org.vaadin.spring.i18n.annotation.EnableI18N; + +/** + * @author lukas + * @since 15.08.18 + */ +@EnableI18N +@Configuration +public class I18nConfiguration { + + @Bean + MessageProvider messageProvider() { + return new ResourceBundleMessageProvider("i18n.com.faendir.acra.messages"); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nIntStepper.java b/src/main/java/com/faendir/acra/i18n/I18nIntStepper.java new file mode 100644 index 0000000..721041c --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nIntStepper.java @@ -0,0 +1,58 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.faendir.acra.i18n; + +import org.vaadin.risto.stepper.IntStepper; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nIntStepper extends IntStepper implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nIntStepper(int value) { + this(value, null, null); + } + + public I18nIntStepper(int value, I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + setValue(value); + if (i18n != null) { + updateMessageStrings(i18n.getLocale()); + } + } + + @Override + public void updateMessageStrings(Locale locale) { + if (i18n != null) { + setCaption(i18n.get(captionId, locale, params)); + } + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nLabel.java b/src/main/java/com/faendir/acra/i18n/I18nLabel.java new file mode 100644 index 0000000..c7de11b --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nLabel.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.shared.ui.ContentMode; +import com.vaadin.ui.Label; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nLabel extends Label implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nLabel(ContentMode contentMode, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + setContentMode(contentMode); + } + + public I18nLabel(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nMenuBar.java b/src/main/java/com/faendir/acra/i18n/I18nMenuBar.java new file mode 100644 index 0000000..9b5d08f --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nMenuBar.java @@ -0,0 +1,74 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.server.Resource; +import com.vaadin.ui.MenuBar; +import org.springframework.lang.Nullable; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 16.08.18 + */ +public class I18nMenuBar extends MenuBar implements Translatable { + private final I18N i18n; + + public I18nMenuBar(I18N i18n) { + this.i18n = i18n; + } + + public I18nMenuItem addItem(Resource icon) { + I18nMenuItem item = new I18nMenuItem(icon, null, i18n, Messages.BLANK); + getItems().add(item); + return item; + } + + @Override + public void updateMessageStrings(Locale locale) { + getItems().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale)); + } + + public class I18nMenuItem extends MenuItem implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + private I18nMenuItem(@Nullable Resource icon, Command command, I18N i18n, String captionId, Object... params) { + //noinspection ConstantConditions + super(i18n.get(captionId, params), icon, command); + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + } + + public I18nMenuItem addItem(Command command, String captionId, Object... params) { + I18nMenuItem item = new I18nMenuItem(null, command, i18n, captionId, params); + getItems().add(item); + return item; + } + + @Override + public void updateMessageStrings(Locale locale) { + setText(i18n.get(captionId, params)); + getItems().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale)); + } + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nPanel.java b/src/main/java/com/faendir/acra/i18n/I18nPanel.java new file mode 100644 index 0000000..a50be22 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nPanel.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.faendir.acra.i18n; + +import com.vaadin.ui.Component; +import com.vaadin.ui.Panel; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 16.08.18 + */ +public class I18nPanel extends Panel implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nPanel(Component content, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + setContent(content); + } + + public I18nPanel(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nPasswordField.java b/src/main/java/com/faendir/acra/i18n/I18nPasswordField.java new file mode 100644 index 0000000..d925e28 --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nPasswordField.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.PasswordField; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 16.08.18 + */ +public class I18nPasswordField extends PasswordField implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nPasswordField(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nSlider.java b/src/main/java/com/faendir/acra/i18n/I18nSlider.java new file mode 100644 index 0000000..a7230ee --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nSlider.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.faendir.acra.i18n; + +import com.vaadin.ui.Slider; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nSlider extends Slider implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nSlider(int min, int max, I18N i18n, String captionId, Object... params) { + super(min, max); + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nTextArea.java b/src/main/java/com/faendir/acra/i18n/I18nTextArea.java new file mode 100644 index 0000000..3d8934e --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nTextArea.java @@ -0,0 +1,45 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.TextArea; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 16.08.18 + */ +public class I18nTextArea extends TextArea implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nTextArea(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } +} diff --git a/src/main/java/com/faendir/acra/i18n/I18nTextField.java b/src/main/java/com/faendir/acra/i18n/I18nTextField.java new file mode 100644 index 0000000..e6690bf --- /dev/null +++ b/src/main/java/com/faendir/acra/i18n/I18nTextField.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.faendir.acra.i18n; + +import com.vaadin.ui.TextField; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; + +import java.util.Locale; + +/** + * @author lukas + * @since 15.08.18 + */ +public class I18nTextField extends TextField implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; + + public I18nTextField(String value, I18N i18n, String captionId, Object... params) { + this(i18n, captionId, params); + setValue(value); + } + + public I18nTextField(I18N i18n, String captionId, Object... params) { + this.i18n = i18n; + this.captionId = captionId; + this.params = params; + updateMessageStrings(i18n.getLocale()); + } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } +} diff --git a/src/main/java/com/faendir/acra/ui/BackendUI.java b/src/main/java/com/faendir/acra/ui/BackendUI.java index 8fc567f..810aa29 100644 --- a/src/main/java/com/faendir/acra/ui/BackendUI.java +++ b/src/main/java/com/faendir/acra/ui/BackendUI.java @@ -15,6 +15,9 @@ */ package com.faendir.acra.ui; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.I18nMenuBar; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.User; import com.faendir.acra.security.SecurityUtils; import com.faendir.acra.ui.navigation.NavigationManager; @@ -35,10 +38,8 @@ import com.vaadin.ui.Alignment; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.LoginForm; -import com.vaadin.ui.MenuBar; import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; -import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.AcraTheme; import com.vaadin.ui.themes.DarkAcraTheme; @@ -53,6 +54,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.util.UriComponentsBuilder; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.TranslatableUI; import java.util.Optional; @@ -64,17 +67,19 @@ import java.util.Optional; @Theme(AcraTheme.THEME_NAME) @Widgetset("com.faendir.acra.AppWidgetset") @Viewport("width=device-width, initial-scale=1") -public class BackendUI extends UI { +public class BackendUI extends TranslatableUI { private static final String DARK_THEME = "dark"; @NonNull private final AuthenticationManager authenticationManager; @NonNull private final ApplicationContext applicationContext; + @NonNull private final I18N i18n; @NonNull private final Panel content; @NonNull private final Path path; @Autowired - public BackendUI(@NonNull AuthenticationManager authenticationManager, @NonNull ApplicationContext applicationContext) { + public BackendUI(@NonNull AuthenticationManager authenticationManager, @NonNull ApplicationContext applicationContext, @NonNull I18N i18n) { this.authenticationManager = authenticationManager; this.applicationContext = applicationContext; + this.i18n = i18n; content = new Panel(); content.setSizeFull(); content.addStyleNames(AcraTheme.NO_PADDING, AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER); @@ -82,7 +87,7 @@ public class BackendUI extends UI { } @Override - protected void init(VaadinRequest request) { + protected void initUI(VaadinRequest request) { if (isDarkTheme()) { setTheme(DarkAcraTheme.THEME_NAME); } @@ -97,14 +102,14 @@ public class BackendUI extends UI { try { Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password)); if (!token.getAuthorities().contains(User.Role.USER)) { - throw new InsufficientAuthenticationException("Missing required role"); + throw new InsufficientAuthenticationException(i18n.get(Messages.MISSING_ROLE)); } VaadinService.reinitializeSession(VaadinService.getCurrentRequest()); SecurityContextHolder.getContext().setAuthentication(token); showMain(); getPushConfiguration().setPushMode(PushMode.AUTOMATIC); } catch (AuthenticationException ex) { - Notification.show("Unknown username/password combination", Notification.Type.ERROR_MESSAGE); + Notification.show(i18n.get(Messages.LOGIN_FAILED), Notification.Type.ERROR_MESSAGE); } } @@ -127,21 +132,23 @@ public class BackendUI extends UI { private void showMain() { NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class); - MenuBar menuBar = new MenuBar(); - MenuBar.MenuItem user = menuBar.addItem("", VaadinIcons.USER, null); + I18nMenuBar menuBar = new I18nMenuBar(i18n); + I18nMenuBar.I18nMenuItem user = menuBar.addItem(VaadinIcons.USER); user.addItem(SecurityUtils.getUsername()).setEnabled(false); user.addSeparator(); - MenuBar.MenuItem theme = user.addItem("Dark Theme", - e -> getPage().setLocation(UriComponentsBuilder.fromUri(getPage().getLocation()).replaceQueryParam(DARK_THEME, e.isChecked()).build().toUri())); + I18nMenuBar.I18nMenuItem theme = user.addItem(e -> getPage().setLocation(UriComponentsBuilder.fromUri(getPage().getLocation()) + .replaceQueryParam(DARK_THEME, e.isChecked()) + .build() + .toUri()), Messages.DARK_THEME); theme.setCheckable(true); theme.setChecked(isDarkTheme()); user.addSeparator(); if (SecurityUtils.hasRole(User.Role.ADMIN)) { - user.addItem("User Manager", e -> navigationManager.cleanNavigateTo(UserManagerView.class)); + user.addItem(e -> navigationManager.cleanNavigateTo(UserManagerView.class), Messages.USER_MANAGER); user.addSeparator(); } - user.addItem("Change Password", e -> navigationManager.cleanNavigateTo(ChangePasswordView.class)); - user.addItem("Logout", e -> logout()); + user.addItem(e -> navigationManager.cleanNavigateTo(ChangePasswordView.class), Messages.CHANGE_PASSWORD); + user.addItem(e -> logout(), Messages.LOGOUT); HorizontalLayout header = new HorizontalLayout(path, menuBar); header.setExpandRatio(path, 1); @@ -150,9 +157,7 @@ public class BackendUI extends UI { HorizontalLayout footer = new HorizontalLayout(); footer.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER); - Label footerLabel = new Label( - "Acrarium is developed by F43nd1r." + " Code is licensed under" - + " Apache License v2.", ContentMode.HTML); + Label footerLabel = new I18nLabel(ContentMode.HTML, i18n, Messages.FOOTER); footerLabel.setWidth(100, Unit.PERCENTAGE); footerLabel.addStyleName(AcraTheme.CENTER_TEXT); footer.addComponent(footerLabel); diff --git a/src/main/java/com/faendir/acra/ui/navigation/NavigationManager.java b/src/main/java/com/faendir/acra/ui/navigation/NavigationManager.java index bcfe98f..38aaee4 100644 --- a/src/main/java/com/faendir/acra/ui/navigation/NavigationManager.java +++ b/src/main/java/com/faendir/acra/ui/navigation/NavigationManager.java @@ -16,6 +16,7 @@ package com.faendir.acra.ui.navigation; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.ui.view.ErrorView; import com.faendir.acra.ui.view.base.navigation.BaseView; import com.faendir.acra.ui.view.base.navigation.Path; @@ -28,6 +29,7 @@ import org.springframework.beans.factory.annotation.Configurable; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; +import org.vaadin.spring.i18n.I18N; import java.io.Serializable; import java.util.Deque; @@ -47,7 +49,7 @@ public class NavigationManager implements Serializable { @NonNull private final MyNavigator navigator; @Autowired - public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator) { + public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator, @NonNull I18N i18n) { this.path = mainPath; this.navigator = navigator; this.navigator.init(ui, mainView); @@ -57,7 +59,7 @@ public class NavigationManager implements Serializable { SingleViewProvider provider = (SingleViewProvider) hierarchy.getLast().getProvider(); String title = provider.getTitle(provider.getParameters(hierarchy.getLast().getNavState())); if (hierarchy.size() > 1) { - title += " - Acrarium"; + title += " - " + i18n.get(Messages.ACRARIUM); } ui.getPage().setTitle(title); }); diff --git a/src/main/java/com/faendir/acra/ui/view/ErrorView.java b/src/main/java/com/faendir/acra/ui/view/ErrorView.java index 9b85178..4334bea 100644 --- a/src/main/java/com/faendir/acra/ui/view/ErrorView.java +++ b/src/main/java/com/faendir/acra/ui/view/ErrorView.java @@ -16,6 +16,8 @@ package com.faendir.acra.ui.view; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.Messages; import com.vaadin.navigator.View; import com.vaadin.navigator.ViewChangeListener; import com.vaadin.spring.annotation.SpringComponent; @@ -24,6 +26,8 @@ import com.vaadin.ui.Alignment; import com.vaadin.ui.Composite; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.i18n.I18N; /** * @author Lukas @@ -32,9 +36,10 @@ import com.vaadin.ui.VerticalLayout; @SpringComponent @UIScope public class ErrorView extends Composite implements View { - public ErrorView() { + @Autowired + public ErrorView(I18N i18n) { VerticalLayout layout = new VerticalLayout(); - Label label = new Label("This page does not exist or you do not have the permission to view it."); + Label label = new I18nLabel(i18n, Messages.ERROR4XX); layout.addComponent(label); layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER); layout.setSizeFull(); diff --git a/src/main/java/com/faendir/acra/ui/view/Overview.java b/src/main/java/com/faendir/acra/ui/view/Overview.java index 044b41f..9d71ebd 100644 --- a/src/main/java/com/faendir/acra/ui/view/Overview.java +++ b/src/main/java/com/faendir/acra/ui/view/Overview.java @@ -15,6 +15,12 @@ */ package com.faendir.acra.ui.view; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.I18nCheckBox; +import com.faendir.acra.i18n.I18nIntStepper; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.I18nTextField; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.QApp; import com.faendir.acra.model.QBug; import com.faendir.acra.model.QReport; @@ -36,16 +42,14 @@ import com.vaadin.spring.annotation.UIScope; import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; -import com.vaadin.ui.CheckBox; import com.vaadin.ui.Grid; import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.AcraTheme; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; -import org.vaadin.risto.stepper.IntStepper; +import org.vaadin.spring.i18n.I18N; /** * @author Lukas @@ -55,50 +59,52 @@ import org.vaadin.risto.stepper.IntStepper; @ViewScope public class Overview extends BaseView { @NonNull private final DataService dataService; + @NonNull private final I18N i18n; private MyGrid grid; @Autowired - public Overview(@NonNull DataService dataService) { + public Overview(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public void enter(ViewChangeListener.ViewChangeEvent event) { - grid = new MyGrid<>("Apps", dataService.getAppProvider()); + grid = new MyGrid<>(dataService.getAppProvider(), i18n, Messages.APPS); grid.setResponsive(true); grid.setSizeToRows(); grid.setSelectionMode(Grid.SelectionMode.NONE); - grid.addColumn(VApp::getName, QApp.app.name, "Name"); - grid.addColumn(VApp::getBugCount, QBug.bug.countDistinct(), "Bugs"); - grid.addColumn(VApp::getReportCount, QReport.report.count(), "Reports"); + grid.addColumn(VApp::getName, QApp.app.name, Messages.NAME); + grid.addColumn(VApp::getBugCount, QBug.bug.countDistinct(), Messages.BUGS); + grid.addColumn(VApp::getReportCount, QReport.report.count(), Messages.REPORTS); grid.addOnClickNavigation(getNavigationManager(), AppView.class, e -> String.valueOf(e.getItem().getId())); VerticalLayout layout = new VerticalLayout(); if (SecurityUtils.hasRole(User.Role.ADMIN)) { - Button add = new Button("New App", e -> { - TextField name = new TextField("Name"); - new Popup().setTitle("New App").addComponent(name).addCreateButton(popup -> { - popup.clear().addComponent(new ConfigurationLabel(dataService.createNewApp(name.getValue()))).addCloseButton().show(); + Button add = new I18nButton(i18n, Messages.NEW_APP); + add.addClickListener(e -> { + TextField name = new I18nTextField(i18n, Messages.NAME); + new Popup(i18n, Messages.NEW_APP).addComponent(name).addCreateButton(popup -> { + popup.clear().addComponent(new ConfigurationLabel(dataService.createNewApp(name.getValue()), i18n)).addCloseButton().show(); grid.getDataProvider().refreshAll(); }).show(); }); - Button importButton = new Button("Import from Acralyzer", e -> { - TextField host = new TextField("Host", "localhost"); - IntStepper port = new IntStepper("Port"); - port.setValue(5984); + Button importButton = new I18nButton(i18n, Messages.IMPORT_ACRALYZER); + importButton.addClickListener(e -> { + I18nTextField host = new I18nTextField("localhost", i18n, Messages.HOST); + I18nIntStepper port = new I18nIntStepper(5984, i18n, Messages.PORT); port.setMinValue(0); port.setMaxValue(65535); - CheckBox ssl = new CheckBox("Use ssl", false); - TextField databaseName = new TextField("Database Name", "acra-myapp"); - new Popup().setTitle("Import from Acralyzer") - .addValidatedField(ValidatedField.of(host), true) + I18nCheckBox ssl = new I18nCheckBox(i18n, Messages.SSL); + I18nTextField databaseName = new I18nTextField("acra-myapp", i18n, Messages.DATABASE_NAME); + new Popup(i18n, Messages.IMPORT_ACRALYZER).addValidatedField(ValidatedField.of(host), true) .addValidatedField(ValidatedField.of(port), true) .addValidatedField(ValidatedField.of(ssl), true) .addValidatedField(ValidatedField.of(databaseName), true) .addCreateButton(popup -> { ImportResult importResult = dataService.importFromAcraStorage(host.getValue(), port.getValue(), ssl.getValue(), databaseName.getValue()); popup.clear() - .addComponent(new Label(String.format("Import successful for %d of %d reports.", importResult.getSuccessCount(), importResult.getTotalCount()))) - .addComponent(new ConfigurationLabel(importResult.getUser())) + .addComponent(new I18nLabel(i18n, Messages.IMPORT_SUCCESS, importResult.getSuccessCount(), importResult.getTotalCount())) + .addComponent(new ConfigurationLabel(importResult.getUser(), i18n)) .addCloseButton() .show(); grid.getDataProvider().refreshAll(); @@ -120,13 +126,16 @@ public class Overview extends BaseView { @SpringComponent @UIScope public static class Provider extends SingleViewProvider { - protected Provider() { + @NonNull private final I18N i18n; + + protected Provider(@NonNull I18N i18n) { super(Overview.class); + this.i18n = i18n; } @Override public String getTitle(String parameter) { - return "Acrarium"; + return i18n.get(Messages.ACRARIUM); } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/AppView.java b/src/main/java/com/faendir/acra/ui/view/app/AppView.java index 3eb75a2..4218a7d 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/AppView.java +++ b/src/main/java/com/faendir/acra/ui/view/app/AppView.java @@ -56,10 +56,9 @@ public class AppView extends ParametrizedBaseView> { protected void enter(@NonNull Pair parameter) { MyTabSheet tabSheet = new MyTabSheet<>(parameter.getFirst(), getNavigationManager(), tabs); tabSheet.setSizeFull(); - tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(parameter.getFirst().getId() + "/" + e.getTabSheet().getSelectedTab().getCaption())); - tabSheet.addMiddleClickListener(e -> getNavigationManager().openInNewTab(parameter.getFirst().getId() + "/" + e.getTab().getCaption())); - if (tabSheet.getCaptions().contains(parameter.getSecond())) tabSheet.setInitialTab(parameter.getSecond()); - else tabSheet.setFirstTabAsInitialTab(); + tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(parameter.getFirst().getId() + "/" + e.getTabSheet().getSelectedTab().getId())); + tabSheet.addMiddleClickListener(e -> getNavigationManager().openInNewTab(parameter.getFirst().getId() + "/" + e.getTab().getId())); + tabSheet.guessInitialTab(parameter.getSecond()); Panel panel = new Panel(tabSheet); panel.setSizeFull(); panel.addStyleNames(AcraTheme.NO_BORDER, AcraTheme.NO_BACKGROUND, AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT); diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/AdminTab.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/AdminTab.java index d4069e6..53a30cb 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/AdminTab.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/AdminTab.java @@ -15,6 +15,7 @@ */ package com.faendir.acra.ui.view.app.tabs; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.Permission; import com.faendir.acra.ui.annotation.RequiresAppPermission; @@ -24,6 +25,7 @@ import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; import java.util.List; @@ -35,14 +37,22 @@ import java.util.List; @SpringComponent @ViewScope public class AdminTab extends PanelFlexTab implements AppTab { + @NonNull private final I18N i18n; + @Autowired - public AdminTab(@NonNull List panels) { + public AdminTab(@NonNull List panels, @NonNull I18N i18n) { super(panels); + this.i18n = i18n; } @Override public String getCaption() { - return "Admin"; + return i18n.get(Messages.ADMIN); + } + + @Override + public String getId() { + return "admin"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/BugTab.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/BugTab.java index c6daf3c..1e322a4 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/BugTab.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/BugTab.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.faendir.acra.ui.view.app.tabs; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.I18nCheckBox; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.Permission; import com.faendir.acra.model.QBug; @@ -45,6 +47,7 @@ import com.vaadin.ui.renderers.ComponentRenderer; import com.vaadin.ui.themes.AcraTheme; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; import java.util.ArrayList; import java.util.List; @@ -58,15 +61,22 @@ import java.util.stream.Collectors; @ViewScope public class BugTab implements AppTab { @NonNull private final DataService dataService; + @NonNull private final I18N i18n; @Autowired - public BugTab(@NonNull DataService dataService) { + public BugTab(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public String getCaption() { - return "Bugs"; + return i18n.get(Messages.BUGS); + } + + @Override + public String getId() { + return "bug"; } @Override @@ -77,38 +87,39 @@ public class BugTab implements AppTab { header.addStyleName(AcraTheme.PADDING_TOP); layout.addComponent(header); layout.setComponentAlignment(header, Alignment.MIDDLE_LEFT); - CheckBox hideSolved = new CheckBox("Hide solved", true); - MyGrid bugs = new MyGrid<>(null, dataService.getBugProvider(app, hideSolved::getValue)); + CheckBox hideSolved = new I18nCheckBox(true, i18n, Messages.HIDE_SOLVED); + MyGrid bugs = new MyGrid<>(dataService.getBugProvider(app, hideSolved::getValue)); bugs.setSelectionMode(Grid.SelectionMode.MULTI); hideSolved.addValueChangeListener(e -> layout.getUI().access(() -> { bugs.deselectAll(); bugs.getDataProvider().refreshAll(); })); - Button merge = new Button("Merge bugs", e -> { + Button merge = new I18nButton(e -> { List selectedItems = new ArrayList<>(bugs.getSelectedItems()); if (selectedItems.size() > 1) { RadioButtonGroup titles = new RadioButtonGroup<>("", selectedItems.stream().map(bug -> bug.getBug().getTitle()).collect(Collectors.toList())); titles.setSelectedItem(selectedItems.get(0).getBug().getTitle()); - new Popup().setTitle("Choose title for bug group").addComponent(titles).addCreateButton(p -> { + new Popup(i18n, Messages.CHOOSE_BUG_GROUP_TITLE).addComponent(titles).addCreateButton(p -> { dataService.mergeBugs(selectedItems.stream().map(VBug::getBug).collect(Collectors.toList()), titles.getSelectedItem().orElseThrow(IllegalStateException::new)); bugs.deselectAll(); bugs.getDataProvider().refreshAll(); }, true).show(); } else { - Notification.show("Please select at least two bugs", Notification.Type.ERROR_MESSAGE); + Notification.show(i18n.get(Messages.ONLY_ONE_BUG_SELECTED), Notification.Type.ERROR_MESSAGE); } - }); + }, i18n, Messages.MERGE_BUGS); header.addComponent(merge); header.addComponent(hideSolved); header.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT); - bugs.addColumn(VBug::getReportCount,QReport.report.count(), "Reports"); - bugs.sort(bugs.addColumn(VBug::getLastReport, new TimeSpanRenderer(), QReport.report.date.max(), "Latest Report"), SortDirection.DESCENDING); - bugs.addColumn(VBug::getHighestVersionCode, QReport.report.stacktrace.version.code.max(), "Latest Version"); - bugs.addColumn(VBug::getUserCount, QReport.report.installationId.countDistinct(), "Affected Users"); - bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, "Title").setExpandRatio(1).setMinimumWidthFromContent(false); + bugs.addColumn(VBug::getReportCount, QReport.report.count(), Messages.REPORTS); + bugs.sort(bugs.addColumn(VBug::getLastReport, new TimeSpanRenderer(), QReport.report.date.max(), Messages.LATEST_REPORT), SortDirection.DESCENDING); + bugs.addColumn(VBug::getHighestVersionCode, QReport.report.stacktrace.version.code.max(), Messages.LATEST_VERSION); + bugs.addColumn(VBug::getUserCount, QReport.report.installationId.countDistinct(), Messages.AFFECTED_USERS); + bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, Messages.TITLE).setExpandRatio(1).setMinimumWidthFromContent(false); bugs.addOnClickNavigation(navigationManager, com.faendir.acra.ui.view.bug.BugView.class, bugItemClick -> String.valueOf(bugItemClick.getItem().getBug().getId())); - bugs.addColumn(bug -> new MyCheckBox(bug.getBug().isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> dataService.setBugSolved(bug.getBug(), e.getValue())), - new ComponentRenderer(), QBug.bug.solved, "Solved"); + bugs.addColumn(bug -> new MyCheckBox(bug.getBug().isSolved(), + SecurityUtils.hasPermission(app, Permission.Level.EDIT), + e -> dataService.setBugSolved(bug.getBug(), e.getValue())), new ComponentRenderer(), QBug.bug.solved, Messages.SOLVED); layout.addComponent(bugs); layout.setExpandRatio(bugs, 1); layout.setSizeFull(); diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/ReportTab.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/ReportTab.java index a1bf697..d87665c 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/ReportTab.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/ReportTab.java @@ -16,6 +16,7 @@ package com.faendir.acra.ui.view.app.tabs; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.service.AvatarService; import com.faendir.acra.service.DataService; @@ -26,6 +27,7 @@ import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.Component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; /** * @author Lukas @@ -36,23 +38,30 @@ import org.springframework.lang.NonNull; public class ReportTab implements AppTab { @NonNull private final DataService dataService; @NonNull private final AvatarService avatarService; + @NonNull private final I18N i18n; @Autowired - public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) { + public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) { this.dataService = dataService; this.avatarService = avatarService; + this.i18n = i18n; } @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { - Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app)); + Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app), i18n); content.setSizeFull(); return content; } @Override public String getCaption() { - return ReportList.CAPTION; + return i18n.get(Messages.REPORTS); + } + + @Override + public String getId() { + return "report"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java index 2b6f3cd..1c694e4 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java @@ -16,6 +16,7 @@ package com.faendir.acra.ui.view.app.tabs; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.QReport; import com.faendir.acra.service.DataService; @@ -28,6 +29,7 @@ import com.vaadin.ui.Panel; import com.vaadin.ui.themes.AcraTheme; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; /** * @author Lukas @@ -37,15 +39,17 @@ import org.springframework.lang.NonNull; @ViewScope public class StatisticsTab implements AppTab { @NonNull private final DataService dataService; + @NonNull private final I18N i18n; @Autowired - public StatisticsTab(@NonNull DataService dataService) { + public StatisticsTab(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { - Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.app.eq(app), dataService)); + Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.app.eq(app), dataService, i18n)); root.setSizeFull(); root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER); return root; @@ -53,7 +57,12 @@ public class StatisticsTab implements AppTab { @Override public String getCaption() { - return "Statistics"; + return i18n.get(Messages.STATISTICS); + } + + @Override + public String getId() { + return "statistics"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/DangerPanel.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/DangerPanel.java index ff37610..ae8cbfa 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/DangerPanel.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/DangerPanel.java @@ -15,6 +15,10 @@ */ package com.faendir.acra.ui.view.app.tabs.panels; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.I18nSlider; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.QReport; import com.faendir.acra.service.DataService; @@ -33,13 +37,12 @@ import com.vaadin.ui.Button; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; -import com.vaadin.ui.Slider; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.AcraTheme; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.vaadin.risto.stepper.IntStepper; +import org.vaadin.spring.i18n.I18N; /** * @author lukas @@ -49,31 +52,29 @@ import org.vaadin.risto.stepper.IntStepper; @ViewScope public class DangerPanel implements AdminPanel { @NonNull private final DataService dataService; + @NonNull private final I18N i18n; @Autowired - public DangerPanel(@NonNull DataService dataService) { + public DangerPanel(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { - Button configButton = new Button("Create new ACRA Configuration", - e -> new Popup().setTitle("Confirm") - .addComponent(new Label("Are you sure you want to create a new ACRA configuration?
The existing configuration will be invalidated", ContentMode.HTML)) - .addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(dataService.recreateReporterUser(app))).addCloseButton().show()) - .show()); + Button configButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(ContentMode.HTML, i18n, Messages.NEW_ACRA_CONFIG_CONFIRM)) + .addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(dataService.recreateReporterUser(app), i18n)).addCloseButton().show()) + .show(), i18n, Messages.NEW_ACRA_CONFIG); configButton.setWidth(100, Sizeable.Unit.PERCENTAGE); - Button matchingButton = new Button("Configure bug matching", e -> { + Button matchingButton = new I18nButton(e -> { App.Configuration configuration = app.getConfiguration(); - Slider score = new Slider("Minimum score", 0, 100); + I18nSlider score = new I18nSlider(0, 100, i18n, Messages.MIN_SCORE); score.setValue((double) configuration.getMinScore()); - new Popup().addValidatedField(ValidatedField.of(score), true) - .addComponent(new Label("Are you sure you want to save this configuration? " + "All bugs will be checked for merging, which may take some time. " - + "Note that a higher minimum score does not unmerge already merged bugs.")) - .addYesNoButtons(p -> dataService.changeConfiguration(app, - new App.Configuration(score.getValue().intValue())), true) + new Popup(i18n, Messages.CONFIRM).addValidatedField(ValidatedField.of(score), true) + .addComponent(new I18nLabel(i18n, Messages.NEW_BUG_CONFIG_CONFIRM)) + .addYesNoButtons(p -> dataService.changeConfiguration(app, new App.Configuration(score.getValue().intValue())), true) .show(); - }); + }, i18n, Messages.NEW_BUG_CONFIG); matchingButton.setWidth(100, Sizeable.Unit.PERCENTAGE); IntStepper age = new IntStepper(); age.setValue(30); @@ -83,7 +84,10 @@ public class DangerPanel implements AdminPanel { purgeAge.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER); purgeAge.setWidth(100, Sizeable.Unit.PERCENTAGE); purgeAge.addStyleName(AcraTheme.NO_MARGIN); - purgeAge.addComponents(new Button("Purge", e -> dataService.deleteReportsOlderThanDays(app, age.getValue())), new Label(" Reports older than "), age, new Label(" Days")); + purgeAge.addComponents(new I18nButton(e -> dataService.deleteReportsOlderThanDays(app, age.getValue()), i18n, Messages.PURGE), + new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN1), + age, + new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN2)); purgeAge.setExpandRatio(age, 1); ComboBox versionBox = new ComboBox<>(null, dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.stacktrace.version.code)); versionBox.setEmptySelectionAllowed(false); @@ -92,17 +96,16 @@ public class DangerPanel implements AdminPanel { purgeVersion.setWidth(100, Sizeable.Unit.PERCENTAGE); purgeVersion.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER); purgeVersion.addStyleName(AcraTheme.NO_MARGIN); - purgeVersion.addComponents(new Button("Purge", e -> { + purgeVersion.addComponents(new I18nButton(e -> { if (versionBox.getValue() != null) { dataService.deleteReportsBeforeVersion(app, versionBox.getValue()); } - }), new Label(" Reports before Version "), versionBox); + }, i18n, Messages.PURGE), new I18nLabel(i18n, Messages.REPORTS_BEFORE_VERSION), versionBox); purgeVersion.setExpandRatio(versionBox, 1); - Button deleteButton = new Button("Delete App", - e -> new Popup().setTitle("Confirm").addComponent(new Label("Are you sure you want to delete this app and all its associated content?")).addYesNoButtons(popup -> { - dataService.delete(app); - navigationManager.navigateBack(); - }, true).show()); + Button deleteButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(i18n, Messages.DELETE_APP_CONFIRM)).addYesNoButtons(popup -> { + dataService.delete(app); + navigationManager.navigateBack(); + }, true).show(), i18n, Messages.DELETE_APP); VerticalLayout layout = new VerticalLayout(configButton, matchingButton, purgeAge, purgeVersion, deleteButton); deleteButton.setWidth(100, Sizeable.Unit.PERCENTAGE); layout.setSizeFull(); @@ -112,7 +115,12 @@ public class DangerPanel implements AdminPanel { @Override public String getCaption() { - return "Danger Zone"; + return i18n.get(Messages.DANGER_ZONE); + } + + @Override + public String getId() { + return "danger-zone"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ExportPanel.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ExportPanel.java index 7328a57..5687717 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ExportPanel.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ExportPanel.java @@ -16,6 +16,9 @@ package com.faendir.acra.ui.view.app.tabs.panels; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.I18nComboBox; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.QReport; import com.faendir.acra.rest.RestReportInterface; @@ -33,6 +36,7 @@ import com.vaadin.ui.VerticalLayout; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.springframework.web.util.UriComponentsBuilder; +import org.vaadin.spring.i18n.I18N; /** * @author lukas @@ -42,22 +46,24 @@ import org.springframework.web.util.UriComponentsBuilder; @ViewScope public class ExportPanel implements AdminPanel { @NonNull private final DataService dataService; + @NonNull private final I18N i18n; @Autowired - public ExportPanel(@NonNull DataService dataService) { + public ExportPanel(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { - ComboBox mailBox = new ComboBox<>("By Email Address", dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.userEmail)); + ComboBox mailBox = new I18nComboBox<>(dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.userEmail), i18n, Messages.BY_MAIL); mailBox.setEmptySelectionAllowed(true); mailBox.setSizeFull(); - ComboBox idBox = new ComboBox<>("By Installation ID", dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.installationId)); + ComboBox idBox = new I18nComboBox<>(dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.installationId), i18n, Messages.BY_ID); idBox.setEmptySelectionAllowed(true); idBox.setSizeFull(); - Button download = new Button("Download", e -> { + Button download = new I18nButton(e -> { if (idBox.getValue() == null && mailBox.getValue() == null) { - Notification.show("Nothing selected", Notification.Type.WARNING_MESSAGE); + Notification.show(i18n.get(Messages.NOTHING_SELECTED), Notification.Type.WARNING_MESSAGE); } else { Page page = UI.getCurrent().getPage(); page.open(UriComponentsBuilder.fromUri(page.getLocation()) @@ -69,14 +75,19 @@ public class ExportPanel implements AdminPanel { .build() .toUriString(), null); } - }); + }, i18n, Messages.DOWNLOAD); download.setSizeFull(); return new VerticalLayout(mailBox, idBox, download); } @Override public String getCaption() { - return "Export"; + return i18n.get(Messages.EXPORT); + } + + @Override + public String getId() { + return "export"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ProguardPanel.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ProguardPanel.java index 22c702e..caf119b 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ProguardPanel.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/panels/ProguardPanel.java @@ -16,6 +16,10 @@ package com.faendir.acra.ui.view.app.tabs.panels; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.I18nIntStepper; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.Permission; import com.faendir.acra.model.ProguardMapping; @@ -31,9 +35,7 @@ import com.vaadin.icons.VaadinIcons; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; -import com.vaadin.ui.Button; import com.vaadin.ui.Component; -import com.vaadin.ui.Label; import com.vaadin.ui.ProgressBar; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.renderers.ButtonRenderer; @@ -41,6 +43,7 @@ import com.vaadin.ui.themes.AcraTheme; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.vaadin.risto.stepper.IntStepper; +import org.vaadin.spring.i18n.I18N; /** * @author lukas @@ -50,20 +53,22 @@ import org.vaadin.risto.stepper.IntStepper; @ViewScope public class ProguardPanel implements AdminPanel{ @NonNull private final DataService dataService; + @NonNull private final I18N i18n; @Autowired - public ProguardPanel(@NonNull DataService dataService) { + public ProguardPanel(@NonNull DataService dataService, @NonNull I18N i18n) { this.dataService = dataService; + this.i18n = i18n; } @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { VerticalLayout layout = new VerticalLayout(); - MyGrid grid = new MyGrid<>(null, dataService.getMappingProvider(app)); + MyGrid grid = new MyGrid<>(dataService.getMappingProvider(app)); grid.setSizeToRows(); - grid.sort(grid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, "Version"), SortDirection.ASCENDING); + grid.sort(grid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, Messages.VERSION), SortDirection.ASCENDING); if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) { - ButtonRenderer renderer = new ButtonRenderer<>(e -> new Popup().setTitle("Confirm") - .addComponent(new Label("Are you sure you want to delete the mapping for version " + e.getItem().getVersionCode() + "?")) + ButtonRenderer renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM) + .addComponent(new I18nLabel(i18n, Messages.DELETE_MAPPING_CONFIRM, e.getItem().getVersionCode())) .addYesNoButtons(p -> { dataService.delete(e.getItem()); grid.getDataProvider().refreshAll(); @@ -76,30 +81,34 @@ public class ProguardPanel implements AdminPanel{ layout.addComponent(grid); layout.addStyleName(AcraTheme.NO_PADDING); if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) { - layout.addComponent(new Button("Add File", e -> { - IntStepper version = new IntStepper("Version code"); - version.setValue(dataService.getMaximumMappingVersion(app).map(i -> i + 1).orElse(1)); - InMemoryUpload upload = new InMemoryUpload("Mapping file:"); + layout.addComponent(new I18nButton(e -> { + IntStepper version = new I18nIntStepper(dataService.getMaximumMappingVersion(app).map(i -> i + 1).orElse(1), i18n, Messages.VERSION_CODE); + InMemoryUpload upload = new InMemoryUpload(i18n, Messages.MAPPING_FILE); ProgressBar progressBar = new ProgressBar(); upload.addProgressListener((readBytes, contentLength) -> layout.getUI().access(() -> progressBar.setValue((float) readBytes / contentLength))); - new Popup().setTitle("New Mapping Configuration") + new Popup(i18n, Messages.NEW_MAPPING) .addComponent(version) .addValidatedField(ValidatedField.of(upload, () -> upload, consumer -> upload.addFinishedListener(event -> consumer.accept(upload))) - .addValidator(InMemoryUpload::isUploaded, "Upload failed")) + .addValidator(InMemoryUpload::isUploaded, Messages.ERROR_UPLOAD)) .addComponent(progressBar) .addCreateButton(popup -> { dataService.store(new ProguardMapping(app, version.getValue(), upload.getUploadedString())); grid.getDataProvider().refreshAll(); }, true) .show(); - })); + }, i18n, Messages.NEW_FILE)); } return layout; } @Override public String getCaption() { - return "De-Obfuscation"; + return i18n.get(Messages.DE_OBFUSCATION); + } + + @Override + public String getId() { + return "proguard"; } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/base/ConfigurationLabel.java b/src/main/java/com/faendir/acra/ui/view/base/ConfigurationLabel.java index b25f153..8bc412c 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/ConfigurationLabel.java +++ b/src/main/java/com/faendir/acra/ui/view/base/ConfigurationLabel.java @@ -16,23 +16,21 @@ package com.faendir.acra.ui.view.base; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.rest.RestReportInterface; import com.faendir.acra.util.PlainTextUser; import com.faendir.acra.util.Utils; import com.vaadin.shared.ui.ContentMode; -import com.vaadin.ui.Label; +import org.vaadin.spring.i18n.I18N; /** * @author Lukas * @since 18.12.2017 */ -public class ConfigurationLabel extends Label { - public ConfigurationLabel(PlainTextUser user) { - super(String.format("Take note of the following ACRA configuration. It cannot be viewed later:
" - + "@AcraCore(reportFormat = StringFormat.JSON)
" - + "@AcraHttpSender(uri = \"%s%s\",
" - + "basicAuthLogin = \"%s\",
" - + "basicAuthPassword = \"%s\",
" - + "httpMethod = HttpSender.Method.POST)
", Utils.getUrlWithFragment(null), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword()), ContentMode.HTML); +public class ConfigurationLabel extends I18nLabel { + public ConfigurationLabel(PlainTextUser user, I18N i18n) { + super(i18n, Messages.CONFIGURATION_LABEL, Utils.getUrlWithFragment(null), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword()); + setContentMode(ContentMode.HTML); } } diff --git a/src/main/java/com/faendir/acra/ui/view/base/InMemoryUpload.java b/src/main/java/com/faendir/acra/ui/view/base/InMemoryUpload.java index 7923c56..f938146 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/InMemoryUpload.java +++ b/src/main/java/com/faendir/acra/ui/view/base/InMemoryUpload.java @@ -16,26 +16,36 @@ package com.faendir.acra.ui.view.base; +import com.faendir.acra.i18n.HasI18n; import com.vaadin.ui.Upload; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; import java.io.ByteArrayOutputStream; +import java.util.Locale; /** * @author Lukas * @since 19.12.2017 */ -public class InMemoryUpload extends Upload { +public class InMemoryUpload extends Upload implements Translatable, HasI18n { + private final I18N i18n; + private final String captionId; + private final Object[] params; private final ByteArrayOutputStream outputStream; private boolean finished; - public InMemoryUpload(String caption) { + public InMemoryUpload(I18N i18n, String captionId, Object... params) { super(); + this.i18n = i18n; + this.captionId = captionId; + this.params = params; outputStream = new ByteArrayOutputStream(); finished = false; - setCaption(caption); setReceiver((filename, mimeType) -> outputStream); addSucceededListener(event -> finished = true); addFailedListener(event -> finished = false); + updateMessageStrings(i18n.getLocale()); } public boolean isUploaded() { @@ -45,4 +55,14 @@ public class InMemoryUpload extends Upload { public String getUploadedString() { return outputStream.toString(); } + + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + } + + @Override + public I18N getI18n() { + return i18n; + } } diff --git a/src/main/java/com/faendir/acra/ui/view/base/ReportList.java b/src/main/java/com/faendir/acra/ui/view/base/ReportList.java index 0dd992b..c60c181 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/ReportList.java +++ b/src/main/java/com/faendir/acra/ui/view/base/ReportList.java @@ -16,6 +16,8 @@ package com.faendir.acra.ui.view.base; import com.faendir.acra.dataprovider.QueryDslDataProvider; +import com.faendir.acra.i18n.I18nLabel; +import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; import com.faendir.acra.model.Permission; import com.faendir.acra.model.QReport; @@ -30,10 +32,10 @@ import com.faendir.acra.util.TimeSpanRenderer; import com.vaadin.icons.VaadinIcons; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.ui.Grid; -import com.vaadin.ui.Label; import com.vaadin.ui.renderers.ButtonRenderer; import com.vaadin.ui.renderers.ImageRenderer; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; import java.util.function.Consumer; @@ -42,22 +44,20 @@ import java.util.function.Consumer; * @since 14.05.2017 */ public class ReportList extends MyGrid { - public static final String CAPTION = "Reports"; - public ReportList(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull AvatarService avatarService, @NonNull Consumer reportDeleter, @NonNull QueryDslDataProvider reportProvider) { - super(CAPTION, reportProvider); - setId(CAPTION); + public ReportList(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull AvatarService avatarService, @NonNull Consumer reportDeleter, @NonNull QueryDslDataProvider reportProvider, I18N i18n) { + super(reportProvider, i18n, Messages.REPORTS); setSelectionMode(Grid.SelectionMode.NONE); - addColumn(avatarService::getAvatar, new ImageRenderer<>(), QReport.report.installationId, "User"); - sort(addColumn(Report::getDate, new TimeSpanRenderer(), QReport.report.date, "Date"), SortDirection.DESCENDING); - addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, "App Version"); - addColumn(Report::getAndroidVersion, QReport.report.androidVersion, "Android Version"); - addColumn(Report::getPhoneModel, QReport.report.phoneModel, "Device"); - addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, "Stacktrace").setExpandRatio(1) + addColumn(avatarService::getAvatar, new ImageRenderer<>(), QReport.report.installationId, Messages.USER); + sort(addColumn(Report::getDate, new TimeSpanRenderer(), QReport.report.date, Messages.DATE), SortDirection.DESCENDING); + addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, Messages.APP_VERSION); + addColumn(Report::getAndroidVersion, QReport.report.androidVersion, Messages.ANDROID_VERSION); + addColumn(Report::getPhoneModel, QReport.report.phoneModel, Messages.DEVICE); + addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, Messages.STACKTRACE).setExpandRatio(1) .setMinimumWidthFromContent(false); if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) { - ButtonRenderer renderer = new ButtonRenderer<>(e -> new Popup().setTitle("Confirm") - .addComponent(new Label("Are you sure you want to delete this report?")) + ButtonRenderer renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM) + .addComponent(new I18nLabel(i18n, Messages.DELETE_REPORT_CONFIRM)) .addYesNoButtons(p -> { reportDeleter.accept(e.getItem()); getDataProvider().refreshAll(); diff --git a/src/main/java/com/faendir/acra/ui/view/base/layout/ComponentFactory.java b/src/main/java/com/faendir/acra/ui/view/base/layout/ComponentFactory.java index 2a420dd..fd479be 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/layout/ComponentFactory.java +++ b/src/main/java/com/faendir/acra/ui/view/base/layout/ComponentFactory.java @@ -29,4 +29,6 @@ public interface ComponentFactory extends Ordered { Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager); String getCaption(); + + String getId(); } diff --git a/src/main/java/com/faendir/acra/ui/view/base/layout/MyGrid.java b/src/main/java/com/faendir/acra/ui/view/base/layout/MyGrid.java index d38a595..4c4e99a 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/layout/MyGrid.java +++ b/src/main/java/com/faendir/acra/ui/view/base/layout/MyGrid.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.faendir.acra.ui.view.base.layout; import com.faendir.acra.client.mygrid.GridMiddleClickExtensionConnector; @@ -30,10 +29,16 @@ import com.vaadin.ui.Grid; import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.TextRenderer; import elemental.json.JsonObject; +import org.springframework.data.util.Pair; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; import java.util.Collection; import java.util.EventObject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -41,37 +46,58 @@ import java.util.function.Function; * @author Lukas * @since 14.05.2017 */ -public class MyGrid extends Composite { +public class MyGrid extends Composite implements Translatable { + private final I18N i18n; + private final String captionId; + private final Object[] params; + private final Map, Pair> columnCaptions; private final ExposingGrid grid; private final QueryDslDataProvider dataProvider; - public MyGrid(String caption, QueryDslDataProvider dataProvider) { + public MyGrid(QueryDslDataProvider dataProvider) { + this(dataProvider, null, null); + } + + public MyGrid(QueryDslDataProvider dataProvider, I18N i18n, String captionId, Object... params) { this.dataProvider = dataProvider; - grid = new ExposingGrid<>(caption, dataProvider); + grid = new ExposingGrid<>(dataProvider); setCompositionRoot(grid); setSizeFull(); MiddleClickExtension.extend(this); + this.i18n = i18n; + this.captionId = captionId; + columnCaptions = new HashMap<>(); + this.params = params; + if (i18n != null) { + updateMessageStrings(i18n.getLocale()); + } } @NonNull - public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull String caption) { - return addColumn(valueProvider, new TextRenderer(), caption); + public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull String captionId, Object... params) { + Grid.Column column = addColumn(valueProvider, new TextRenderer(), i18n.get(captionId, params)); + columnCaptions.put(column, Pair.of(captionId, params)); + return column; } @NonNull - public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull AbstractRenderer renderer, @NonNull String caption) { - return grid.addColumn(valueProvider, renderer).setCaption(caption).setSortable(false); + public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull AbstractRenderer renderer, @NonNull String captionId, Object... params) { + Grid.Column column = addColumn(valueProvider, renderer).setCaption(i18n.get(captionId, params)); + columnCaptions.put(column, Pair.of(captionId, params)); + return column; } @NonNull - public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull Expression sort, @NonNull String caption) { - return addColumn(valueProvider, new TextRenderer(), sort, caption); + public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull Expression sort, @NonNull String captionId, Object... params) { + return addColumn(valueProvider, new TextRenderer(), sort, captionId, params); } @NonNull public Grid.Column addColumn(@NonNull ValueProvider valueProvider, @NonNull AbstractRenderer renderer, - @NonNull Expression sort, @NonNull String caption) { - return grid.addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(caption); + @NonNull Expression sort, @NonNull String captionId, Object... params) { + Grid.Column column = grid.addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(i18n.get(captionId, params)); + columnCaptions.put(column, Pair.of(captionId, params)); + return column; } @NonNull @@ -116,8 +142,7 @@ public class MyGrid extends Composite { grid.deselectAll(); } - public void addOnClickNavigation(@NonNull NavigationManager navigationManager, Class namedView, - Function, String> parameterGetter) { + public void addOnClickNavigation(@NonNull NavigationManager navigationManager, Class namedView, Function, String> parameterGetter) { grid.addItemClickListener(e -> { boolean newTab = e.getMouseEventDetails().getButton() == MouseEventDetails.MouseButton.MIDDLE || e.getMouseEventDetails().isCtrlKey(); navigationManager.navigateTo(namedView, parameterGetter.apply(e), newTab); @@ -128,6 +153,12 @@ public class MyGrid extends Composite { return dataProvider; } + @Override + public void updateMessageStrings(Locale locale) { + setCaption(i18n.get(captionId, locale, params)); + columnCaptions.forEach((column, caption) -> column.setCaption(i18n.get(caption.getFirst(), locale, caption.getSecond()))); + } + public static class MiddleClickExtension extends Grid.AbstractGridExtension { private MiddleClickExtension(MyGrid myGrid) { ExposingGrid grid = myGrid.grid; @@ -161,8 +192,8 @@ public class MyGrid extends Composite { } private static class ExposingGrid extends Grid { - ExposingGrid(String caption, DataProvider dataProvider) { - super(caption, dataProvider); + ExposingGrid(DataProvider dataProvider) { + super(dataProvider); } @Override diff --git a/src/main/java/com/faendir/acra/ui/view/base/layout/MyTabSheet.java b/src/main/java/com/faendir/acra/ui/view/base/layout/MyTabSheet.java index 7a1cc8a..238a478 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/layout/MyTabSheet.java +++ b/src/main/java/com/faendir/acra/ui/view/base/layout/MyTabSheet.java @@ -29,10 +29,6 @@ import org.springframework.lang.NonNull; import java.lang.reflect.Method; import java.util.Collection; -import java.util.List; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; /** @@ -52,27 +48,14 @@ public class MyTabSheet extends TabSheet { MiddleClickExtension.extend(this); } - public List getCaptions() { - return StreamSupport.stream(Spliterators.spliterator(iterator(), getComponentCount(), Spliterator.ORDERED), false).map(Component::getCaption).collect(Collectors.toList()); - } - public void addTab(ComponentFactory tab) { TabWrapper wrapper = new TabWrapper<>(t, navigationManager, tab); addComponent(wrapper); addSelectedTabChangeListener(wrapper); } - public void setInitialTab(String caption) { - StreamSupport.stream(spliterator(), false).filter(c -> c.getCaption().equals(caption)).findAny().ifPresent(component -> { - if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) { - ((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true)); - } - setSelectedTab(component); - }); - } - - public void setFirstTabAsInitialTab() { - Component component = getTab(0).getComponent(); + public void guessInitialTab(String id) { + Component component = StreamSupport.stream(spliterator(), false).filter(c -> c.getId().equals(id)).findAny().orElseGet(() -> getTab(0).getComponent()); if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) { ((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true)); } diff --git a/src/main/java/com/faendir/acra/ui/view/base/popup/Popup.java b/src/main/java/com/faendir/acra/ui/view/base/popup/Popup.java index f5db57b..691d1fb 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/popup/Popup.java +++ b/src/main/java/com/faendir/acra/ui/view/base/popup/Popup.java @@ -16,6 +16,8 @@ package com.faendir.acra.ui.view.base.popup; +import com.faendir.acra.i18n.I18nButton; +import com.faendir.acra.i18n.Messages; import com.vaadin.ui.Button; import com.vaadin.ui.Component; import com.vaadin.ui.FormLayout; @@ -25,10 +27,13 @@ import com.vaadin.ui.Window; import com.vaadin.ui.themes.AcraTheme; import org.springframework.data.util.Pair; import org.springframework.lang.NonNull; +import org.vaadin.spring.i18n.I18N; +import org.vaadin.spring.i18n.support.Translatable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -36,21 +41,22 @@ import java.util.function.Consumer; * @author Lukas * @since 19.12.2017 */ -public class Popup extends Window { +public class Popup extends Window implements Translatable { private final List components; private final Map, Pair> fields; private final List