Exact picture seems to work

This commit is contained in:
2025-09-02 21:53:03 +02:00
parent f15208fe6d
commit b2571c191f
137 changed files with 2487 additions and 797 deletions

View File

@@ -8,8 +8,10 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -22,7 +24,8 @@ public abstract class AbstractFetcher implements Fetcher {
private static final Pattern FILENAME_SPLIT = Pattern.compile("__");
private final BirdProvider birdProvider;
private final Configuration configuration;
private final FetcherConfiguration configuration;
private final Set<Bird> fetched;
/**
* Instantiates the fetcher
@@ -31,9 +34,10 @@ public abstract class AbstractFetcher implements Fetcher {
* @param configuration The configuration
* @throws NullPointerException If any parameter is null
*/
protected AbstractFetcher(final BirdProvider birdProvider, final Configuration configuration) {
protected AbstractFetcher(final BirdProvider birdProvider, final FetcherConfiguration configuration) {
this.birdProvider = Objects.requireNonNull(birdProvider);
this.configuration = Objects.requireNonNull(configuration);
this.fetched = new HashSet<>();
}
@Override
@@ -61,6 +65,7 @@ public abstract class AbstractFetcher implements Fetcher {
@Override
public FilledBird fetch(final Bird bird) throws FetchException {
if (!isAlreadyFetched(bird)) {
fetched.add(bird);
logger.info("Fetching {} ({})", bird, name());
download(bird);
}
@@ -169,6 +174,9 @@ public abstract class AbstractFetcher implements Fetcher {
}
private boolean isAlreadyFetched(final Bird bird) {
if (fetched.contains(bird)) {
return true;
}
final var path = getCachePath(bird);
if (Files.isDirectory(path)) {
try (final var found = Files.find(path, Integer.MAX_VALUE, (p, bfa) -> bfa.isRegularFile())) {

View File

@@ -0,0 +1,23 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.Bird;
import java.util.Objects;
/**
* Implementation of {@link Bird}
*
* @param name The bird name
*/
public record BirdImpl(String name) implements Bird {
/**
* Instantiates the bird
*
* @param name The bird name
* @throws NullPointerException If any parameter is null
*/
public BirdImpl {
Objects.requireNonNull(name);
}
}

View File

@@ -13,6 +13,7 @@ import java.util.Locale;
*/
public class CommonBirdsProvider implements BirdProvider {
/**
* Instantiates the provider
*/

View File

@@ -4,6 +4,8 @@ import ch.gtache.fro.Bird;
import ch.gtache.fro.BirdTranslator;
import jakarta.inject.Inject;
import java.util.Locale;
/**
* Implementation of {@link BirdTranslator} for {@link CommonBirds}
*/
@@ -14,6 +16,15 @@ public class CommonBirdsTranslatorImpl extends AbstractBundleTranslator<Bird> im
super("ch.gtache.fro.impl.BirdBundle");
}
@Override
public String translate(final Bird object, final Locale locale) {
if (object instanceof CommonBirds) {
return super.translate(object, locale);
} else {
return object.name();
}
}
@Override
protected String getKey(final Bird object) {
return "bird." + object.name() + ".label";

View File

@@ -1,157 +0,0 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.Bird;
import ch.gtache.fro.BirdProvider;
import ch.gtache.fro.Configuration;
import ch.gtache.fro.PictureType;
import ch.gtache.fro.ProvisionException;
import ch.gtache.fro.SoundType;
import ch.gtache.fro.practice.BirdPracticeParameters;
import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl;
import jakarta.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link Configuration}
*/
public class ConfigurationImpl implements Configuration {
private static final Logger logger = LogManager.getLogger(ConfigurationImpl.class);
private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds");
private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000);
private static final String ROOT_FOLDER_KEY = "rootFolder";
private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch";
private final BirdProvider birdProvider;
private final Preferences preferences;
private Path rootFolder;
private Duration waitBetweenFetch;
private final Map<Bird, BirdPracticeParameters> birdPracticeParameters;
@Inject
ConfigurationImpl(final BirdProvider birdProvider) {
this.birdProvider = requireNonNull(birdProvider);
this.rootFolder = DEFAULT_ROOT_FOLDER;
this.waitBetweenFetch = DEFAULT_WAIT_BETWEEN_FETCH;
this.preferences = Preferences.userNodeForPackage(this.getClass());
this.birdPracticeParameters = new HashMap<>();
load();
}
private void load() {
this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString()));
this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis()));
try {
final var allBirds = birdProvider.getAllObjects();
final var practiceParameters = allBirds.stream().map(this::loadPracticeParameters).collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e));
birdPracticeParameters.putAll(practiceParameters);
} catch (final ProvisionException e) {
logger.error("Failed to get all birds", e);
}
logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch);
}
@Override
public Path rootFolder() {
return rootFolder;
}
@Override
public void setRootFolder(final Path rootFolder) {
logger.info("Setting root folder to {}", rootFolder);
this.rootFolder = requireNonNull(rootFolder);
preferences.put(ROOT_FOLDER_KEY, rootFolder.toString());
}
@Override
public Duration waitBetweenFetch() {
return waitBetweenFetch;
}
@Override
public void setWaitBetweenFetch(final Duration waitBetweenFetch) {
logger.info("Setting wait between fetch to {}", waitBetweenFetch);
this.waitBetweenFetch = requireNonNull(waitBetweenFetch);
preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis());
}
@Override
public Collection<BirdPracticeParameters> birdPracticeParameters() {
return birdPracticeParameters.values();
}
@Override
public BirdPracticeParameters birdPracticeParameters(final Bird bird) {
return birdPracticeParameters.get(bird);
}
private BirdPracticeParameters loadPracticeParameters(final Bird bird) {
final var parameters = getAllParameters(bird);
final var birdParameters = parameters.stream().collect(Collectors.toMap(e -> e, e -> preferences.get(e, getDefaultValue(e))));
return convertParameters(bird, birdParameters);
}
@Override
public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) {
this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters);
final var parameters = convertParameters(birdPracticeParameters);
parameters.forEach(preferences::put);
}
private static Map<String, String> convertParameters(final BirdPracticeParameters parameters) {
final var enabled = parameters.enabled();
final var enabledFetchers = String.join(";", parameters.enabledFetchers());
final var enabledPictureTypes = parameters.enabledPictureTypes().stream().map(PictureType::name).collect(Collectors.joining(";"));
final var enabledSoundTypes = parameters.enabledSoundTypes().stream().map(SoundType::name).collect(Collectors.joining(";"));
final var name = parameters.bird().name();
return Map.of(
name + "_enabled", String.valueOf(enabled),
name + "_enabledFetchers", enabledFetchers,
name + "_enabledPictureTypes", enabledPictureTypes,
name + "_enabledSoundTypes", enabledSoundTypes
);
}
private static BirdPracticeParameters convertParameters(final Bird bird, final Map<String, String> parameters) {
final var enabled = Boolean.parseBoolean(parameters.get(bird.name() + "_enabled"));
final var enabledFetchers = Arrays.stream(parameters.get(bird.name() + "_enabledFetchers").split(";")).filter(e -> !e.isBlank()).collect(Collectors.toSet());
final var enabledPictureTypes = Arrays.stream(parameters.get(bird.name() + "_enabledPictureTypes").split(";")).filter(e -> !e.isBlank()).map(PictureType::valueOf).collect(Collectors.toSet());
final var enabledSoundTypes = Arrays.stream(parameters.get(bird.name() + "_enabledSoundTypes").split(";")).filter(e -> !e.isBlank()).map(SoundType::valueOf).collect(Collectors.toSet());
return new BirdPracticeParametersImpl(bird, enabled, enabledFetchers, enabledPictureTypes, enabledSoundTypes);
}
private static String getDefaultValue(final String parameter) {
final var type = parameter.split("_")[1];
return switch (type) {
case "enabled" -> "true";
case "enabledFetchers", "enabledPictureTypes", "enabledSoundTypes" -> "all";
default -> "";
};
}
private static Set<String> getAllParameters(final Bird bird) {
final var name = bird.name();
return Set.of(
name + "_enabled",
name + "_enabledFetchers",
name + "_enabledPictureTypes",
name + "_enabledSoundTypes"
);
}
}

View File

@@ -0,0 +1,68 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.FetcherConfiguration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.time.Duration;
import java.util.prefs.Preferences;
import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link FetcherConfiguration}
*/
public class FetcherConfigurationImpl implements FetcherConfiguration {
private static final Logger logger = LogManager.getLogger(FetcherConfigurationImpl.class);
private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds");
private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000);
private static final String ROOT_FOLDER_KEY = "rootFolder";
private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch";
private final Preferences preferences;
private Path rootFolder;
private Duration waitBetweenFetch;
/**
* Instantiates the configuration
*
* @param fetcherName The name of the fetcher
* @throws NullPointerException If any parameter is null
*/
public FetcherConfigurationImpl(final String fetcherName) {
this.preferences = Preferences.userNodeForPackage(this.getClass()).node(fetcherName);
load();
}
private void load() {
this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString()));
this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis()));
logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch);
}
@Override
public Path rootFolder() {
return rootFolder;
}
@Override
public void setRootFolder(final Path rootFolder) {
logger.info("Setting root folder to {}", rootFolder);
this.rootFolder = requireNonNull(rootFolder);
preferences.put(ROOT_FOLDER_KEY, rootFolder.toString());
}
@Override
public Duration waitBetweenFetch() {
return waitBetweenFetch;
}
@Override
public void setWaitBetweenFetch(final Duration waitBetweenFetch) {
logger.info("Setting wait between fetch to {}", waitBetweenFetch);
this.waitBetweenFetch = requireNonNull(waitBetweenFetch);
preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis());
}
}

View File

@@ -0,0 +1,213 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.Bird;
import ch.gtache.fro.BirdProvider;
import ch.gtache.fro.Fetcher;
import ch.gtache.fro.FetcherProvider;
import ch.gtache.fro.PictureType;
import ch.gtache.fro.ProvisionException;
import ch.gtache.fro.SoundType;
import ch.gtache.fro.practice.BirdPracticeParameters;
import ch.gtache.fro.practice.PracticeConfiguration;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link PracticeConfiguration}
*/
public class PracticeConfigurationImpl implements PracticeConfiguration {
private static final Logger logger = LogManager.getLogger(PracticeConfigurationImpl.class);
private static final int DEFAULT_GUESS_NUMBER = 1;
private static final String QUESTION_RATES_KEY = "questionRates";
private static final String BIRD_PRACTICE_PARAMETERS_KEY = "birdPracticeParameters";
private static final String GUESSES_NUMBER_KEY = "guessNumber";
private final BirdProvider birdProvider;
private final FetcherProvider fetcherProvider;
private final ObjectMapper objectMapper;
private final Preferences preferences;
private final Map<Bird, BirdPracticeParameters> birdPracticeParameters;
private final Map<QuestionType, Double> questionRates;
private int guessesNumber;
@Inject
PracticeConfigurationImpl(final BirdProvider birdProvider, final FetcherProvider fetcherProvider, final ObjectMapper objectMapper) {
this.birdProvider = requireNonNull(birdProvider);
this.fetcherProvider = requireNonNull(fetcherProvider);
this.objectMapper = requireNonNull(objectMapper);
this.preferences = Preferences.userNodeForPackage(this.getClass());
this.birdPracticeParameters = new HashMap<>();
this.questionRates = new EnumMap<>(QuestionType.class);
this.guessesNumber = DEFAULT_GUESS_NUMBER;
load();
}
private void load() {
loadBirdPracticeParameters();
loadQuestionRates();
this.guessesNumber = preferences.getInt(GUESSES_NUMBER_KEY, DEFAULT_GUESS_NUMBER);
}
private void loadQuestionRates() {
final var json = preferences.get(QUESTION_RATES_KEY, "{}");
try {
final var deserializedQuestionRates = objectMapper.readValue(json, new QuestionRatesMapTypeReference());
questionRates.putAll(deserializedQuestionRates);
} catch (final JsonProcessingException e) {
logger.error("Failed to deserialize question rates", e);
}
Arrays.stream(QuestionType.values()).forEach(qt -> {
if (!questionRates.containsKey(qt)) {
questionRates.put(qt, qt == QuestionType.PICTURE_EXACT ? 1.0 : 0.0);
}
});
}
private void loadBirdPracticeParameters() {
final var practiceParameters = preferences.get(BIRD_PRACTICE_PARAMETERS_KEY, "[]");
try {
final var deserializedPracticeParameters = deserializeBirdPracticeParameters(objectMapper.readValue(practiceParameters, new SerializedBirdPracticeParametersTypeReference()));
birdPracticeParameters.putAll(deserializedPracticeParameters);
} catch (final JsonProcessingException e) {
logger.error("Failed to deserialize bird parameters", e);
}
try {
final var allBirds = birdProvider.getAllObjects();
allBirds.forEach(b -> {
if (!birdPracticeParameters.containsKey(b)) {
birdPracticeParameters.put(b, getDefaultParameters(b));
}
});
} catch (final ProvisionException e) {
logger.error("Failed to get all birds", e);
}
}
private Map<Bird, BirdPracticeParameters> deserializeBirdPracticeParameters(final List<SerializedBirdPracticeParameters> parameters) {
return parameters.stream().collect(Collectors.toMap(p -> {
try {
return birdProvider.getObject(p.bird());
} catch (final ProvisionException e) {
logger.error("Failed to get bird {}", p.bird(), e);
return null;
}
}, this::deserializeBirdPracticeParameters));
}
private BirdPracticeParameters deserializeBirdPracticeParameters(final SerializedBirdPracticeParameters parameters) {
try {
final var bird = birdProvider.getObject(parameters.bird());
return new BirdPracticeParametersImpl(bird, parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes());
} catch (final ProvisionException e) {
logger.error("Failed to get bird {}", parameters.bird(), e);
return null;
}
}
private List<SerializedBirdPracticeParameters> serializeBirdPracticeParameters() {
return birdPracticeParameters.values().stream().map(PracticeConfigurationImpl::serializeBirdPracticeParameters).toList();
}
private static SerializedBirdPracticeParameters serializeBirdPracticeParameters(final BirdPracticeParameters parameters) {
return new SerializedBirdPracticeParameters(parameters.bird().name(), parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes());
}
@Override
public int guessesNumber() {
return guessesNumber;
}
@Override
public void setGuessesNumber(final int guessNumber) {
this.guessesNumber = guessNumber;
preferences.putInt(GUESSES_NUMBER_KEY, guessNumber);
}
@Override
public Map<QuestionType, Double> questionRates() {
return new EnumMap<>(questionRates);
}
@Override
public void setQuestionRates(final Map<QuestionType, Double> questionRates) {
this.questionRates.clear();
this.questionRates.putAll(questionRates);
saveQuestionRates();
}
private void saveQuestionRates() {
final String json;
try {
json = objectMapper.writeValueAsString(questionRates);
preferences.put(QUESTION_RATES_KEY, json);
} catch (final JsonProcessingException e) {
logger.error("Failed to serialize question rates", e);
}
}
@Override
public Collection<BirdPracticeParameters> birdPracticeParameters() {
return birdPracticeParameters.values();
}
@Override
public BirdPracticeParameters birdPracticeParameters(final Bird bird) {
return birdPracticeParameters.get(bird);
}
@Override
public void setBirdPracticeParameters(final Collection<? extends BirdPracticeParameters> birdPracticeParameters) {
this.birdPracticeParameters.clear();
this.birdPracticeParameters.putAll(birdPracticeParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, Function.identity())));
saveBirdPracticeParameters();
}
@Override
public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) {
this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters);
saveBirdPracticeParameters();
}
private void saveBirdPracticeParameters() {
final var parameters = serializeBirdPracticeParameters();
try {
final var json = objectMapper.writeValueAsString(parameters);
preferences.put(BIRD_PRACTICE_PARAMETERS_KEY, json);
} catch (final JsonProcessingException e) {
logger.error("Failed to serialize bird parameters", e);
}
}
private BirdPracticeParameters getDefaultParameters(final Bird bird) {
final var pictureTypes = Set.copyOf(Arrays.asList(PictureType.values()));
final var soundTypes = Set.copyOf(Arrays.asList(SoundType.values()));
try {
final var fetcherNames = fetcherProvider.getAllObjects().stream().map(Fetcher::name).collect(Collectors.toSet());
return new BirdPracticeParametersImpl(bird, true, fetcherNames, pictureTypes, soundTypes);
} catch (final ProvisionException e) {
logger.error("Failed to get fetcher names", e);
return new BirdPracticeParametersImpl(bird, true, Set.of(), pictureTypes, soundTypes);
}
}
}

View File

@@ -0,0 +1,9 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.practice.QuestionType;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.Map;
class QuestionRatesMapTypeReference extends TypeReference<Map<QuestionType, Double>> {
}

View File

@@ -0,0 +1,15 @@
package ch.gtache.fro.impl;
import ch.gtache.fro.PictureType;
import ch.gtache.fro.SoundType;
import java.util.Set;
record SerializedBirdPracticeParameters(String bird, boolean enabled, Set<String> enabledFetchers,
Set<PictureType> enabledPictureTypes,
Set<SoundType> enabledSoundTypes) {
SerializedBirdPracticeParameters(final String bird) {
this(bird, false, Set.of(), Set.of(), Set.of());
}
}

View File

@@ -0,0 +1,8 @@
package ch.gtache.fro.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
class SerializedBirdPracticeParametersTypeReference extends TypeReference<List<SerializedBirdPracticeParameters>> {
}

View File

@@ -2,19 +2,21 @@ package ch.gtache.fro.modules.impl;
import ch.gtache.fro.BirdProvider;
import ch.gtache.fro.BirdTranslator;
import ch.gtache.fro.Configuration;
import ch.gtache.fro.FetcherProvider;
import ch.gtache.fro.PictureTypeTranslator;
import ch.gtache.fro.SoundTypeTranslator;
import ch.gtache.fro.impl.CommonBirdsProvider;
import ch.gtache.fro.impl.CommonBirdsTranslatorImpl;
import ch.gtache.fro.impl.ConfigurationImpl;
import ch.gtache.fro.impl.FetcherProviderImpl;
import ch.gtache.fro.impl.PictureTypeTranslatorImpl;
import ch.gtache.fro.impl.PracticeConfigurationImpl;
import ch.gtache.fro.impl.SoundTypeTranslatorImpl;
import ch.gtache.fro.modules.practice.impl.CorePracticeModule;
import ch.gtache.fro.practice.PracticeConfiguration;
import com.fasterxml.jackson.databind.ObjectMapper;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
/**
* Dagger module for the core package
@@ -30,7 +32,7 @@ public abstract class CoreModule {
abstract BirdProvider bindsBirdProvider(final CommonBirdsProvider commonBirdsProvider);
@Binds
abstract Configuration bindsConfiguration(final ConfigurationImpl configurationImpl);
abstract PracticeConfiguration bindsConfiguration(final PracticeConfigurationImpl configurationImpl);
@Binds
abstract FetcherProvider bindsFetcherProvider(final FetcherProviderImpl fetcherProviderImpl);
@@ -43,4 +45,9 @@ public abstract class CoreModule {
@Binds
abstract SoundTypeTranslator bindsSoundTypeTranslator(final SoundTypeTranslatorImpl soundTypeTranslatorImpl);
@Provides
static ObjectMapper providesObjectMapper() {
return new ObjectMapper();
}
}

View File

@@ -2,12 +2,15 @@ package ch.gtache.fro.modules.practice.impl;
import ch.gtache.fro.practice.PracticeQuestionGenerator;
import ch.gtache.fro.practice.PracticeRunner;
import ch.gtache.fro.practice.PracticeTypeTranslator;
import ch.gtache.fro.practice.QuestionTypeTranslator;
import ch.gtache.fro.practice.impl.PracticeQuestionGeneratorImpl;
import ch.gtache.fro.practice.impl.PracticeRunnerImpl;
import ch.gtache.fro.practice.impl.PracticeTypeTranslatorImpl;
import ch.gtache.fro.practice.impl.QuestionTypeTranslatorImpl;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import java.util.Random;
/**
* Dagger module for the core practice package
@@ -26,5 +29,10 @@ public abstract class CorePracticeModule {
abstract PracticeRunner bindsPracticeRunner(final PracticeRunnerImpl practiceRunnerImpl);
@Binds
abstract PracticeTypeTranslator bindsPracticeTypeTranslator(final PracticeTypeTranslatorImpl practiceTypeTranslatorImpl);
abstract QuestionTypeTranslator bindsQuestionTypeTranslator(final QuestionTypeTranslatorImpl questionTypeTranslator);
@Provides
static Random providesRandom() {
return new Random();
}
}

View File

@@ -3,6 +3,7 @@ package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Bird;
import ch.gtache.fro.Picture;
import ch.gtache.fro.practice.PictureMultichoicePracticeQuestion;
import ch.gtache.fro.practice.QuestionType;
import java.util.List;
@@ -28,4 +29,9 @@ public record PictureMultichoicePracticeQuestionImpl(List<Bird> choices,
choices = List.copyOf(choices);
requireNonNull(picture);
}
@Override
public QuestionType questionType() {
return QuestionType.PICTURE_MULTICHOICE;
}
}

View File

@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Picture;
import ch.gtache.fro.practice.PicturePracticeQuestion;
import ch.gtache.fro.practice.QuestionType;
import java.util.Objects;
@@ -21,4 +22,9 @@ public record PicturePracticeQuestionImpl(Picture picture) implements PicturePra
public PicturePracticeQuestionImpl {
Objects.requireNonNull(picture);
}
@Override
public QuestionType questionType() {
return QuestionType.PICTURE_EXACT;
}
}

View File

@@ -3,39 +3,44 @@ package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Bird;
import ch.gtache.fro.practice.BirdPracticeParameters;
import ch.gtache.fro.practice.PracticeParameters;
import ch.gtache.fro.practice.PracticeType;
import ch.gtache.fro.practice.QuestionType;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link PracticeParameters}
*
* @param birdParameters The bird parameters
* @param practiceType The practice type
* @param questionsCount The number of questions
* @param propositionsCount The number of propositions
* @param birdParameters The bird parameters
* @param questionTypeRates The practice type rates
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
* @param questionsNumber The number of questions
* @param propositionsNumber The number of propositions
*/
public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParameters, PracticeType practiceType,
int questionsCount, int propositionsCount) implements PracticeParameters {
public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParameters,
Map<QuestionType, Double> questionTypeRates,
int guessesNumber, int questionsNumber,
int propositionsNumber) implements PracticeParameters {
/**
* Instantiates the parameters
*
* @param birdParameters The bird parameters
* @param practiceType The practice type
* @param questionsCount The number of questions
* @param propositionsCount The number of propositions
* @param birdParameters The bird parameters
* @param questionTypeRates The practice type rates
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
* @param questionsNumber The number of questions
* @param propositionsNumber The number of propositions
* @throws NullPointerException If any parameter is null
* @throws IllegalArgumentException If questionsCount is less than 1
*/
public PracticeParametersImpl {
birdParameters = Map.copyOf(birdParameters);
requireNonNull(practiceType);
if (questionsCount <= 0) {
questionTypeRates = Map.copyOf(questionTypeRates);
if (guessesNumber <= 0) {
throw new IllegalArgumentException("guessesNumber must be > 0");
}
if (questionsNumber <= 0) {
throw new IllegalArgumentException("questionsCount must be > 0");
}
}
@@ -43,17 +48,18 @@ public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParam
/**
* Instantiates the parameters
*
* @param birdParameters The bird parameters
* @param practiceType The practice type
* @param questionsCount The number of questions
* @param propositionsCount The number of propositions
* @param birdParameters The bird parameters
* @param questionTypeRates The practice type rates
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
* @param questionsNumber The number of questions
* @param propositionsNumber The number of propositions
* @throws NullPointerException If any parameter is null
* @throws IllegalArgumentException If questionsCount is less than 1
*/
public PracticeParametersImpl(final Collection<? extends BirdPracticeParameters> birdParameters,
final PracticeType practiceType, final int questionsCount,
final int propositionsCount) {
final Map<QuestionType, Double> questionTypeRates, final int guessesNumber,
final int questionsNumber, final int propositionsNumber) {
this(birdParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e)),
practiceType, questionsCount, propositionsCount);
questionTypeRates, guessesNumber, questionsNumber, propositionsNumber);
}
}

View File

@@ -10,6 +10,7 @@ import ch.gtache.fro.practice.BirdPracticeParameters;
import ch.gtache.fro.practice.PracticeParameters;
import ch.gtache.fro.practice.PracticeQuestion;
import ch.gtache.fro.practice.PracticeQuestionGenerator;
import ch.gtache.fro.practice.QuestionType;
import jakarta.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Stream;
@@ -48,15 +50,15 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator
public PracticeQuestion generate(final PracticeParameters parameters) {
final var enabledBirds = parameters.birdParameters().values().stream()
.filter(p -> p.enabled() && !p.enabledFetchers().isEmpty()).toList();
final var practiceType = parameters.practiceType();
return switch (practiceType) {
final var questionType = getQuestionType(parameters.questionTypeRates());
return switch (questionType) {
case PICTURE_EXACT -> {
final var image = getPicture(enabledBirds);
yield new PicturePracticeQuestionImpl(image);
}
case PICTURE_MULTICHOICE -> {
final var image = getPicture(enabledBirds);
final var propositions = getPropositions(enabledBirds, parameters.propositionsCount());
final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber());
yield new PictureMultichoicePracticeQuestionImpl(propositions, image);
}
case SOUND_EXACT -> {
@@ -65,13 +67,26 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator
}
case SOUND_MULTICHOICE -> {
final var sound = getSound(enabledBirds);
final var propositions = getPropositions(enabledBirds, parameters.propositionsCount());
final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber());
yield new SoundMultichoicePracticeQuestionImpl(propositions, sound);
}
case SOUND_MULTICHOICE_PICTURE -> throw new UnsupportedOperationException("Not implemented yet");
};
}
private QuestionType getQuestionType(final Map<QuestionType, Double> map) {
final var total = map.values().stream().mapToDouble(Double::doubleValue).sum();
final var randomCount = random.nextDouble() * total;
var sum = 0.0;
for (final var entry : map.entrySet()) {
sum += entry.getValue();
if (sum >= randomCount) {
return entry.getKey();
}
}
throw new IllegalStateException("Should never happen");
}
private Sound getSound(final Collection<? extends BirdPracticeParameters> enabledBirds) {
final var sounds = enabledBirds.stream().flatMap(p ->
p.enabledFetchers().stream().flatMap(f -> {

View File

@@ -38,7 +38,7 @@ public class PracticeRunnerImpl implements PracticeRunner {
@Override
public PracticeRun step(final PracticeRun run, final Bird answer) {
if (run.currentQuestionIndex() < run.parameters().questionsCount()) {
if (run.currentQuestionIndex() < run.parameters().questionsNumber()) {
final var question = questionGenerator.generate(run.parameters());
final var currentQuestion = run.currentQuestion();
if (currentQuestion.bird() == answer) {
@@ -57,7 +57,7 @@ public class PracticeRunnerImpl implements PracticeRunner {
@Override
public PracticeResult finish(final PracticeRun run) {
if (run.currentQuestionIndex() == run.parameters().questionsCount()) {
if (run.currentQuestionIndex() == run.parameters().questionsNumber()) {
return new PracticeResultImpl(run.correctQuestions(), run.failedQuestions());
} else {
throw new IllegalStateException("The practice run " + run + " is not finished");

View File

@@ -1,25 +0,0 @@
package ch.gtache.fro.practice.impl;
import ch.gtache.fro.impl.AbstractBundleTranslator;
import ch.gtache.fro.practice.PracticeType;
import ch.gtache.fro.practice.PracticeTypeTranslator;
import jakarta.inject.Inject;
import java.util.Locale;
/**
* Implementation of {@link PracticeTypeTranslator}
*/
public class PracticeTypeTranslatorImpl extends AbstractBundleTranslator<PracticeType>
implements PracticeTypeTranslator {
@Inject
PracticeTypeTranslatorImpl() {
super("ch.gtache.fro.practice.impl.PracticeTypeBundle");
}
@Override
protected String getKey(final PracticeType object) {
return "practice.type." + object.name().toLowerCase(Locale.ROOT) + ".label";
}
}

View File

@@ -0,0 +1,25 @@
package ch.gtache.fro.practice.impl;
import ch.gtache.fro.impl.AbstractBundleTranslator;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.QuestionTypeTranslator;
import jakarta.inject.Inject;
import java.util.Locale;
/**
* Implementation of {@link QuestionTypeTranslator}
*/
public class QuestionTypeTranslatorImpl extends AbstractBundleTranslator<QuestionType>
implements QuestionTypeTranslator {
@Inject
QuestionTypeTranslatorImpl() {
super("ch.gtache.fro.practice.impl.QuestionTypeBundle");
}
@Override
protected String getKey(final QuestionType object) {
return "question.type." + object.name().toLowerCase(Locale.ROOT) + ".label";
}
}

View File

@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Picture;
import ch.gtache.fro.Sound;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.SoundMultichoicePicturePracticeQuestion;
import java.util.List;
@@ -28,4 +29,9 @@ public record SoundMultichoicePicturePracticeQuestionImpl(List<Picture> pictureC
pictureChoices = List.copyOf(pictureChoices);
requireNonNull(sound);
}
@Override
public QuestionType questionType() {
return QuestionType.SOUND_MULTICHOICE_PICTURE;
}
}

View File

@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Bird;
import ch.gtache.fro.Sound;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.SoundMultichoicePracticeQuestion;
import java.util.List;
@@ -28,4 +29,9 @@ public record SoundMultichoicePracticeQuestionImpl(List<Bird> choices,
choices = List.copyOf(choices);
requireNonNull(sound);
}
@Override
public QuestionType questionType() {
return QuestionType.SOUND_MULTICHOICE;
}
}

View File

@@ -1,6 +1,7 @@
package ch.gtache.fro.practice.impl;
import ch.gtache.fro.Sound;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.SoundPracticeQuestion;
import java.util.Objects;
@@ -21,4 +22,9 @@ public record SoundPracticeQuestionImpl(Sound sound) implements SoundPracticeQue
public SoundPracticeQuestionImpl {
Objects.requireNonNull(sound);
}
@Override
public QuestionType questionType() {
return QuestionType.SOUND_EXACT;
}
}

View File

@@ -3,14 +3,18 @@
*/
module ch.gtache.fro.core {
requires transitive ch.gtache.fro.api;
requires java.prefs;
requires transitive com.fasterxml.jackson.core;
requires transitive dagger;
requires org.apache.logging.log4j;
requires dagger;
requires java.prefs;
requires jakarta.inject;
requires com.fasterxml.jackson.databind;
exports ch.gtache.fro.impl;
exports ch.gtache.fro.practice.impl;
exports ch.gtache.fro.modules.impl;
exports ch.gtache.fro.modules.practice.impl;
opens ch.gtache.fro.impl to com.fasterxml.jackson.databind;
}

View File

@@ -0,0 +1,428 @@
bird.ACCENTEUR_ALPIN.label=Alpine Accentor
bird.ACCENTEUR_MOUCHET.label=Dunnock
bird.AGROBATE_ROUX.label=Rufous-tailed Scrub Robin
bird.AIGLE_BOTTE.label=Booted Eagle
bird.AIGLE_CRIARD.label=Greater Spotted Eagle
bird.AIGLE_POMARIN.label=Lesser Spotted Eagle
bird.AIGLE_ROYAL.label=Golden Eagle
bird.AIGRETTE_GARZETTE.label=Little Egret
bird.ALOUETTE_CALANDRE.label=Calandra Lark
bird.ALOUETTE_CALANDRELLE.label=Greater Short-toed Lark
bird.ALOUETTE_DES_CHAMPS.label=Eurasian Skylark
bird.ALOUETTE_HAUSSECOL.label=Horned Lark
bird.ALOUETTE_LEUCOPTERE.label=White-winged Lark
bird.ALOUETTE_LULU.label=Woodlark
bird.ALOUETTE_MONTICOLE.label=Bimaculated Lark
bird.ALOUETTE_PISPOLETTE.label=Mediterranean Short-toed Lark
bird.AUTOUR_DES_PALOMBES.label=Eurasian Goshawk
bird.AVOCETTE_ELEGANTE.label=Pied Avocet
bird.BALBUZARD_PECHEUR.label=Osprey
bird.BARGE_A_QUEUE_NOIRE.label=Black-tailed Godwit
bird.BARGE_ROUSSE.label=Bar-tailed Godwit
bird.BEC_CROISE_DES_SAPINS.label=Red Crossbill
bird.BECASSE_DES_BOIS.label=Eurasian Woodcock
bird.BECASSEAU_COCORLI.label=Curlew Sandpiper
bird.BECASSEAU_DE_BONAPARTE.label=White-rumped Sandpiper
bird.BECASSEAU_DE_TEMMINCK.label=Temminck's Stint
bird.BECASSEAU_FALCINELLE.label=Broad-billed Sandpiper
bird.BECASSEAU_MAUBECHE.label=Red Knot
bird.BECASSEAU_MINUTE.label=Little Stint
bird.BECASSEAU_ROUSSET.label=Buff-breasted Sandpiper
bird.BECASSEAU_SANDERLING.label=Sanderling
bird.BECASSEAU_TACHETE.label=Pectoral Sandpiper
bird.BECASSEAU_VARIABLE.label=Dunlin
bird.BECASSEAU_VIOLET.label=Purple Sandpiper
bird.BECASSINE_DES_MARAIS.label=Common Snipe
bird.BECASSINE_DOUBLE.label=Great Snipe
bird.BECASSINE_SOURDE.label=Jack Snipe
bird.BERGERONNETTE_CITRINE.label=Citrine Wagtail
bird.BERGERONNETTE_DES_RUISSEAUX.label=Grey Wagtail
bird.BERGERONNETTE_GRISE.label=White Wagtail
bird.BERGERONNETTE_PRINTANIERE.label=Western Yellow Wagtail
bird.BERNACHE_A_COU_ROUX.label=Red-breasted Goose
bird.BERNACHE_CRAVANT.label=Brant Goose
bird.BERNACHE_DU_CANADA.label=Canada Goose
bird.BERNACHE_NONNETTE.label=Barnacle Goose
bird.BIHOREAU_GRIS.label=Black-crowned Night Heron
bird.BLONGIOS_NAIN.label=Little Bittern
bird.BONDREE_APIVORE.label=European Honey Buzzard
bird.BOUSCARLE_DE_CETTI.label=Cetti's Warbler
bird.BOUVREUIL_PIVOINE.label=Eurasian Bullfinch
bird.BRUANT_A_CALOTTE_BLANCHE.label=Pine Bunting
bird.BRUANT_CHANTEUR.label=Song Sparrow
bird.BRUANT_DES_NEIGES.label=Snow Bunting
bird.BRUANT_DES_ROSEAUX.label=Common Reed Bunting
bird.BRUANT_FOU.label=Rock Bunting
bird.BRUANT_JAUNE.label=Yellowhammer
bird.BRUANT_LAPON.label=Lapland Longspur
bird.BRUANT_MELANOCEPHALE.label=Black-headed Bunting
bird.BRUANT_NAIN.label=Little Bunting
bird.BRUANT_ORTOLAN.label=Ortolan Bunting
bird.BRUANT_PROYER.label=Corn Bunting
bird.BRUANT_RUSTIQUE.label=Rustic Bunting
bird.BRUANT_ZIZI.label=Cirl Bunting
bird.BUSARD_CENDRE.label=Montagu's Harrier
bird.BUSARD_DES_ROSEAUX.label=Western Marsh Harrier
bird.BUSARD_PALE.label=Pallid Harrier
bird.BUSARD_SAINT_MARTIN.label=Hen Harrier
bird.BUSE_FEROCE.label=Long-legged Buzzard
bird.BUSE_PATTUE.label=Rough-legged Buzzard
bird.BUSE_VARIABLE.label=Common Buzzard
bird.BUTOR_ETOILE.label=Eurasian Bittern
bird.CAILLE_DES_BLES.label=Common Quail
bird.CANARD_CAROLIN.label=Wood Duck
bird.CANARD_CHIPEAU.label=Gadwall
bird.CANARD_COLVERT.label=Mallard
bird.CANARD_MANDARIN.label=Mandarin Duck
bird.CANARD_PILET.label=Northern Pintail
bird.CANARD_SIFFLEUR.label=Eurasian Wigeon
bird.CANARD_SOUCHET.label=Northern Shoveler
bird.CASSENOIX_MOUCHETE.label=Northern Nutcracker
bird.CHARDONNERET_ELEGANT.label=European Goldfinch
bird.CHEVALIER_ABOYEUR.label=Common Greenshank
bird.CHEVALIER_ARLEQUIN.label=Spotted Redshank
bird.CHEVALIER_BARGETTE.label=Terek Sandpiper
bird.CHEVALIER_CULBLANC.label=Green Sandpiper
bird.CHEVALIER_GAMBETTE.label=Common Redshank
bird.CHEVALIER_GRIVELE.label=Spotted Sandpiper
bird.CHEVALIER_GUIGNETTE.label=Common Sandpiper
bird.CHEVALIER_STAGNATILE.label=Marsh Sandpiper
bird.CHEVALIER_SYLVAIN.label=Wood Sandpiper
bird.CHEVECHE_D_ATHENA.label=Little Owl
bird.CHEVECHETTE_D_EUROPE.label=Eurasian Pygmy Owl
bird.CHOCARD_A_BEC_JAUNE.label=Alpine Chough
bird.CHOUCAS_DES_TOURS.label=Western Jackdaw
bird.CHOUETTE_DE_TENGMALM.label=Boreal Owl
bird.CHOUETTE_EPERVIERE.label=Northern Hawk-Owl
bird.CHOUETTE_HULOTTE.label=Tawny Owl
bird.CIGOGNE_BLANCHE.label=White Stork
bird.CIGOGNE_NOIRE.label=Black Stork
bird.CINCLE_PLONGEUR.label=White-throated Dipper
bird.CIRCAETE_JEAN_LE_BLANC.label=Short-toed Snake Eagle
bird.CISTICOLE_DES_JONCS.label=Zitting Cisticola
bird.COCHEVIS_HUPPE.label=Crested Lark
bird.COMBATTANT_VARIE.label=Ruff
bird.CORBEAU_FREUX.label=Rook
bird.CORMORAN_HUPPE.label=European Shag
bird.CORMORAN_PYGMEE.label=Pygmy Cormorant
bird.CORNEILLE_MANTELEE.label=Hooded Crow
bird.CORNEILLE_NOIRE.label=Carrion Crow
bird.COUCOU_GEAI.label=Great Spotted Cuckoo
bird.COUCOU_GRIS.label=Common Cuckoo
bird.COURLIS_A_BEC_GRELE.label=Slender-billed Curlew
bird.COURLIS_CENDRE.label=Eurasian Curlew
bird.COURLIS_CORLIEU.label=Eurasian Whimbrel
bird.COURVITE_ISABELLE.label=Cream-colored Courser
bird.CRABIER_CHEVELU.label=Squacco Heron
bird.CRAVE_A_BEC_ROUGE.label=Red-billed Chough
bird.CYGNE_CHANTEUR.label=Whooper Swan
bird.CYGNE_DE_BEWICK.label=Tundra Swan
bird.CYGNE_TUBERCULE.label=Mute Swan
bird.DURBEC_DES_SAPINS.label=Pine Grosbeak
bird.ECHASSE_BLANCHE.label=Black-winged Stilt
bird.EFFRAIE_DES_CLOCHERS.label=Western Barn Owl
bird.EIDER_A_DUVET.label=Common Eider
bird.ELANION_BLANC.label=Black-winged Kite
bird.ENGOULEVENT_D_EUROPE.label=European Nightjar
bird.EPERVIER_D_EUROPE.label=Eurasian Sparrowhawk
bird.ERISMATURE_A_TETE_BLANCHE.label=White-headed Duck
bird.ERISMATURE_ROUSSE.label=Ruddy Duck
bird.ETOURNEAU_ROSELIN.label=Rosy Starling
bird.ETOURNEAU_SANSONNET.label=Common Starling
bird.ETOURNEAU_UNICOLORE.label=Spotless Starling
bird.FAISAN_DE_COLCHIDE.label=Common Pheasant
bird.FAUCON_CRECERELLE.label=Common Kestrel
bird.FAUCON_CRECERELLETTE.label=Lesser Kestrel
bird.FAUCON_D_ELEONORE.label=Eleonora's Falcon
bird.FAUCON_EMERILLON.label=Merlin
bird.FAUCON_GERFAUT.label=Gyrfalcon
bird.FAUCON_HOBEREAU.label=Eurasian Hobby
bird.FAUCON_KOBEZ.label=Red-footed Falcon
bird.FAUCON_PELERIN.label=Peregrine Falcon
bird.FAUCON_SACRE.label=Saker Falcon
bird.FAUVETTE_A_LUNETTES.label=Spectacled Warbler
bird.FAUVETTE_A_TETE_NOIRE.label=Eurasian Blackcap
bird.FAUVETTE_BABILLARDE.label=Lesser Whitethroat
bird.FAUVETTE_DES_JARDINS.label=Garden Warbler
bird.FAUVETTE_EPERVIERE.label=Barred Warbler
bird.FAUVETTE_GRISETTE.label=Common Whitethroat
bird.FAUVETTE_MELANOCEPHALE.label=Sardinian Warbler
bird.FAUVETTE_ORPHEE.label=Western Orphean Warbler
bird.FAUVETTE_PASSERINETTE.label=Western Subalpine Warbler
bird.FAUVETTE_PITCHOU.label=Dartford Warbler
bird.FLAMANT_ROSE.label=Greater Flamingo
bird.FOU_DE_BASSAN.label=Northern Gannet
bird.FOULQUE_MACROULE.label=Eurasian Coot
bird.FULIGULE_A_BEC_CERCLE.label=Ring-necked Duck
bird.FULIGULE_A_TETE_NOIRE.label=Lesser Scaup
bird.FULIGULE_MILOUIN.label=Common Pochard
bird.FULIGULE_MILOUINAN.label=Greater Scaup
bird.FULIGULE_MORILLON.label=Tufted Duck
bird.FULIGULE_NYROCA.label=Ferruginous Duck
bird.GALLINULE_POULE_D_EAU.label=Common Moorhen
bird.GARROT_A_OEIL_D_OR.label=Common Goldeneye
bird.GEAI_DES_CHENES.label=Eurasian Jay
bird.GELINOTTE_DES_BOIS.label=Hazel Grouse
bird.GLAREOLE_A_AILES_NOIRES.label=Black-winged Pratincole
bird.GLAREOLE_A_COLLIER.label=Collared Pratincole
bird.GOBEMOUCHE_A_COLLIER.label=Collared Flycatcher
bird.GOBEMOUCHE_A_DEMI_COLLIER.label=Semicollared Flycatcher
bird.GOBEMOUCHE_GRIS.label=Spotted Flycatcher
bird.GOBEMOUCHE_NAIN.label=Red-breasted Flycatcher
bird.GOBEMOUCHE_NOIR.label=European Pied Flycatcher
bird.GOELAND_A_AILES_BLANCHES.label=Iceland Gull
bird.GOELAND_ARGENTE.label=European Herring Gull
bird.GOELAND_BOURGMESTRE.label=Glaucous Gull
bird.GOELAND_BRUN.label=Lesser Black-backed Gull
bird.GOELAND_CENDRE.label=Common Gull
bird.GOELAND_D_AUDOUIN.label=Audouin's Gull
bird.GOELAND_LEUCOPHEE.label=Yellow-legged Gull
bird.GOELAND_MARIN.label=Great Black-backed Gull
bird.GOELAND_PONTIQUE.label=Caspian Gull
bird.GOELAND_RAILLEUR.label=Slender-billed Gull
bird.GORGEBLEUE_A_MIROIR.label=Bluethroat
bird.GRAND_CORBEAU.label=Northern Raven
bird.GRAND_CORMORAN.label=Great Cormorant
bird.GRAND_GRAVELOT.label=Common Ringed Plover
bird.GRAND_LABBE.label=Great Skua
bird.GRAND_TETRAS.label=Western Capercaillie
bird.GRAND_DUC_D_EUROPE.label=Eurasian Eagle-Owl
bird.GRANDE_AIGRETTE.label=Great Egret
bird.GRAVELOT_A_COLLIER_INTERROMPU.label=Kentish Plover
bird.GRAVELOT_KILDIR.label=Killdeer
bird.GREBE_A_COU_NOIR.label=Black-necked Grebe
bird.GREBE_CASTAGNEUX.label=Little Grebe
bird.GREBE_ESCLAVON.label=Horned Grebe
bird.GREBE_HUPPE.label=Great Crested Grebe
bird.GREBE_JOUGRIS.label=Red-necked Grebe
bird.GRIMPEREAU_DES_BOIS.label=Eurasian Treecreeper
bird.GRIMPEREAU_DES_JARDINS.label=Short-toed Treecreeper
bird.GRIVE_DE_SIBERIE.label=Siberian Thrush
bird.GRIVE_DRAINE.label=Mistle Thrush
bird.GRIVE_LITORNE.label=Fieldfare
bird.GRIVE_MAUVIS.label=Redwing
bird.GRIVE_MUSICIENNE.label=Song Thrush
bird.GROSBEC_CASSE_NOYAUX.label=Hawfinch
bird.GRUE_CENDREE.label=Common Crane
bird.GUEPIER_D_EUROPE.label=European Bee-eater
bird.GUEPIER_DE_PERSE.label=Blue-cheeked Bee-eater
bird.GUIFETTE_LEUCOPTERE.label=White-winged Tern
bird.GUIFETTE_MOUSTAC.label=Whiskered Tern
bird.GUIFETTE_NOIRE.label=Black Tern
bird.GUILLEMOT_A_LONG_BEC.label=Long-billed Murrelet
bird.GUILLEMOT_DE_TROIL.label=Common Murre
bird.GYPAETE_BARBU.label=Bearded Vulture
bird.HARELDE_BOREALE.label=Long-tailed Duck
bird.HARLE_BIEVRE.label=Common Merganser
bird.HARLE_HUPPE.label=Red-breasted Merganser
bird.HARLE_PIETTE.label=Smew
bird.HERON_CENDRE.label=Grey Heron
bird.HERON_GARDE_BOEUFS.label=Western Cattle Egret
bird.HERON_POURPRE.label=Purple Heron
bird.HIBOU_DES_MARAIS.label=Short-eared Owl
bird.HIBOU_MOYEN_DUC.label=Long-eared Owl
bird.HIRONDELLE_DE_FENETRE.label=Western House Martin
bird.HIRONDELLE_DE_RIVAGE.label=Sand Martin
bird.HIRONDELLE_DE_ROCHERS.label=Eurasian Crag Martin
bird.HIRONDELLE_ROUSSELINE.label=European Red-rumped Swallow
bird.HIRONDELLE_RUSTIQUE.label=Barn Swallow
bird.HUITRIER_PIE.label=Eurasian Oystercatcher
bird.HUPPE_FASCIEE.label=Eurasian Hoopoe
bird.HYPOLAIS_BOTTEE.label=Booted Warbler
bird.HYPOLAIS_ICTERINE.label=Icterine Warbler
bird.HYPOLAIS_POLYGLOTTE.label=Melodious Warbler
bird.IBIS_CHAUVE.label=Northern Bald Ibis
bird.IBIS_FALCINELLE.label=Glossy Ibis
bird.IBIS_SACRE.label=African Sacred Ibis
bird.IRANIE_A_GORGE_BLANCHE.label=White-throated Robin
bird.JASEUR_BOREAL.label=Bohemian Waxwing
bird.LABBE_A_LONGUE_QUEUE.label=Long-tailed Jaeger
bird.LABBE_PARASITE.label=Parasitic Jaeger
bird.LABBE_POMARIN.label=Pomarine Jaeger
bird.LAGOPEDE_ALPIN.label=Rock Ptarmigan
bird.LINOTTE_A_BEC_JAUNE.label=Twite
bird.LINOTTE_MELODIEUSE.label=Common Linnet
bird.LOCUSTELLE_FLUVIATILE.label=River Warbler
bird.LOCUSTELLE_LUSCINIOIDE.label=Savi's Warbler
bird.LOCUSTELLE_TACHETEE.label=Common Grasshopper Warbler
bird.LORIOT_D_EUROPE.label=Eurasian Golden Oriole
bird.LUSCINIOLE_A_MOUSTACHES.label=Moustached Warbler
bird.MACREUSE_BRUNE.label=Velvet Scoter
bird.MACREUSE_NOIRE.label=Common Scoter
bird.MAROUETTE_DE_BAILLON.label=Baillon's Crake
bird.MAROUETTE_PONCTUEE.label=Spotted Crake
bird.MAROUETTE_POUSSIN.label=Little Crake
bird.MARTIN_PECHEUR_D_EUROPE.label=Common Kingfisher
bird.MARTINET_A_VENTRE_BLANC.label=Alpine Swift
bird.MARTINET_NOIR.label=Common Swift
bird.MARTINET_PALE.label=Pallid Swift
bird.MERLE_A_PLASTRON.label=Ring Ouzel
bird.MERLE_NOIR.label=Common Blackbird
bird.MESANGE_ALPESTRE.label=???
bird.MESANGE_A_LONGUE_QUEUE.label=Long-tailed Tit
bird.MESANGE_BLEUE.label=Eurasian Blue Tit
bird.MESANGE_BOREALE.label=Willow Tit
bird.MESANGE_CHARBONNIERE.label=Great Tit
bird.MESANGE_DES_SAULES.label=???
bird.MESANGE_HUPPEE.label=Crested Tit
bird.MESANGE_NOIRE.label=Coal Tit
bird.MESANGE_NONNETTE.label=Marsh Tit
bird.MILAN_NOIR.label=Black Kite
bird.MILAN_ROYAL.label=Red Kite
bird.MOINEAU_CISALPIN.label=Italian Sparrow
bird.MOINEAU_DOMESTIQUE.label=House Sparrow
bird.MOINEAU_ESPAGNOL.label=Spanish Sparrow
bird.MOINEAU_FRIQUET.label=Eurasian Tree Sparrow
bird.MOINEAU_SOULCIE.label=Rock Sparrow
bird.MONTICOLE_BLEU.label=Blue Rock Thrush
bird.MONTICOLE_DE_ROCHE.label=Common Rock Thrush
bird.MOUETTE_ATRICILLE.label=Laughing Gull
bird.MOUETTE_BLANCHE.label=Ivory Gull
bird.MOUETTE_DE_FRANKLIN.label=Franklin's Gull
bird.MOUETTE_DE_SABINE.label=Sabine's Gull
bird.MOUETTE_MELANOCEPHALE.label=Mediterranean Gull
bird.MOUETTE_PYGMEE.label=Little Gull
bird.MOUETTE_RIEUSE.label=Black-headed Gull
bird.MOUETTE_TRIDACTYLE.label=Black-legged Kittiwake
bird.NETTE_ROUSSE.label=Red-crested Pochard
bird.NIVEROLLE_ALPINE.label=White-winged Snowfinch
bird.OCEANITE_CULBLANC.label=Leach's Storm Petrel
bird.OCEANITE_DE_CASTRO.label=Band-rumped Storm Petrel
bird.OCEANITE_TEMPETE.label=European Storm Petrel
bird.OEDICNEME_CRIARD.label=Eurasian Stone-curlew
bird.OIE_A_BEC_COURT.label=Pink-footed Goose
bird.OIE_CENDREE.label=Greylag Goose
bird.OIE_DES_MOISSONS.label=Taiga Bean Goose
bird.OIE_DES_NEIGES.label=Snow Goose
bird.OIE_NAINE.label=Lesser White-fronted Goose
bird.OIE_RIEUSE.label=Greater White-fronted Goose
bird.OUETTE_D_EGYPTE.label=Egyptian Goose
bird.OUTARDE_BARBUE.label=Great Bustard
bird.OUTARDE_CANEPETIERE.label=Little Bustard
bird.OUTARDE_DE_MACQUEEN.label=Asian Houbara
bird.OUTARDE_HOUBARA.label=African Houbara
bird.PANURE_A_MOUSTACHES.label=Bearded Reedling
bird.PELICAN_BLANC.label=Great White Pelican
bird.PELICAN_FRISE.label=Dalmatian Pelican
bird.PELICAN_GRIS.label=Pink-backed Pelican
bird.PERDRIX_BARTAVELLE.label=Rock Partridge
bird.PERDRIX_GRISE.label=Grey Partridge
bird.PERDRIX_ROUGE.label=Red-legged Partridge
bird.PETIT_GRAVELOT.label=Little Ringed Plover
bird.PETIT_DUC_SCOPS.label=Eurasian Scops Owl
bird.PHALAROPE_A_BEC_ETROIT.label=Red-necked Phalarope
bird.PHALAROPE_A_BEC_LARGE.label=Red Phalarope
bird.PHRAGMITE_AQUATIQUE.label=Aquatic Warbler
bird.PHRAGMITE_DES_JONCS.label=Sedge Warbler
bird.PIC_A_DOS_BLANC.label=White-backed Woodpecker
bird.PIC_CENDRE.label=Grey-headed Woodpecker
bird.PIC_EPEICHE.label=Great Spotted Woodpecker
bird.PIC_EPEICHETTE.label=Lesser Spotted Woodpecker
bird.PIC_MAR.label=Middle Spotted Woodpecker
bird.PIC_NOIR.label=Black Woodpecker
bird.PIC_TRIDACTYLE.label=Eurasian Three-toed Woodpecker
bird.PIC_VERT.label=European Green Woodpecker
bird.PIE_BAVARDE.label=Eurasian Magpie
bird.PIE_GRIECHE_A_POITRINE_ROSE.label=Lesser Grey Shrike
bird.PIE_GRIECHE_A_TETE_ROUSSE.label=Woodchat Shrike
bird.PIE_GRIECHE_ECORCHEUR.label=Red-backed Shrike
bird.PIE_GRIECHE_GRISE.label=Great Grey Shrike
bird.PIE_GRIECHE_ISABELLE.label=Isabelline Shrike
bird.PIGEON_BISET_DOMESTIQUE.label=Rock Dove
bird.PIGEON_COLOMBIN.label=Stock Dove
bird.PIGEON_RAMIER.label=Common Wood Pigeon
bird.PINSON_DES_ARBRES.label=Eurasian Chaffinch
bird.PINSON_DU_NORD.label=Brambling
bird.PIPIT_A_DOS_OLIVE.label=Olive-backed Pipit
bird.PIPIT_A_GORGE_ROUSSE.label=Red-throated Pipit
bird.PIPIT_DE_RICHARD.label=Richard's Pipit
bird.PIPIT_DES_ARBRES.label=Tree Pipit
bird.PIPIT_FARLOUSE.label=Meadow Pipit
bird.PIPIT_ROUSSELINE.label=Tawny Pipit
bird.PIPIT_SPIONCELLE.label=Water Pipit
bird.PLONGEON_A_BEC_BLANC.label=Yellow-billed Loon
bird.PLONGEON_ARCTIQUE.label=Black-throated Loon
bird.PLONGEON_CATMARIN.label=Red-throated Loon
bird.PLONGEON_DU_PACIFIQUE.label=Pacific Loon
bird.PLONGEON_IMBRIN.label=Common Loon
bird.PLUVIER_ARGENTE.label=Grey Plover
bird.PLUVIER_DORE.label=European Golden Plover
bird.PLUVIER_FAUVE.label=Pacific Golden Plover
bird.PLUVIER_GUIGNARD.label=Eurasian Dotterel
bird.POUILLOT_A_GRANDS_SOURCILS.label=Yellow-browed Warbler
bird.POUILLOT_BRUN.label=Dusky Warbler
bird.POUILLOT_DE_BONELLI.label=Western Bonelli's Warbler
bird.POUILLOT_DE_HUME.label=Hume's Leaf Warbler
bird.POUILLOT_DE_PALLAS.label=Pallas's Leaf Warbler
bird.POUILLOT_DE_SCHWARZ.label=Radde's Warbler
bird.POUILLOT_DE_SIBERIE.label=Common Chiffchaff (tristis)
bird.POUILLOT_FITIS.label=Willow Warbler
bird.POUILLOT_IBERIQUE.label=Iberian Chiffchaff
bird.POUILLOT_SIFFLEUR.label=Wood Warbler
bird.POUILLOT_VELOCE.label=Common Chiffchaff
bird.POUILLOT_VERDATRE.label=Greenish Warbler
bird.PUFFIN_CENDRE.label=Cory's Shearwater
bird.PUFFIN_DE_SCOPOLI.label=Scopoli's Shearwater
bird.PUFFIN_DES_ANGLAIS.label=Manx Shearwater
bird.PUFFIN_FULIGINEUX.label=Sooty Shearwater
bird.PUFFIN_YELKOUAN.label=Yelkouan Shearwater
bird.PYGARGUE_A_QUEUE_BLANCHE.label=White-tailed Eagle
bird.RALE_D_EAU.label=Water Rail
bird.RALE_DES_GENETS.label=Corn Crake
bird.REMIZ_PENDULINE.label=Eurasian Penduline Tit
bird.ROITELET_A_TRIPLE_BANDEAU.label=Common Firecrest
bird.ROITELET_HUPPE.label=Goldcrest
bird.ROLLIER_D_EUROPE.label=European Roller
bird.ROSELIN_CRAMOISI.label=Common Rosefinch
bird.ROSELIN_GITHAGINE.label=Trumpeter Finch
bird.ROSSIGNOL_PHILOMELE.label=Common Nightingale
bird.ROSSIGNOL_PROGNE.label=Thrush Nightingale
bird.ROUGEGORGE_FAMILIER.label=European Robin
bird.ROUGEQUEUE_A_FRONT_BLANC.label=Common Redstart
bird.ROUGEQUEUE_NOIR.label=Black Redstart
bird.ROUSSEROLLE_DES_BUISSONS.label=Blyth's Reed Warbler
bird.ROUSSEROLLE_EFFARVATTE.label=Common Reed Warbler
bird.ROUSSEROLLE_ISABELLE.label=Paddyfield Warbler
bird.ROUSSEROLLE_TURDOIDE.label=Great Reed Warbler
bird.ROUSSEROLLE_VERDEROLLE.label=Marsh Warbler
bird.SARCELLE_A_AILES_BLEUES.label=Blue-winged Teal
bird.SARCELLE_D_ETE.label=Garganey
bird.SARCELLE_D_HIVER.label=Eurasian Teal
bird.SARCELLE_MARBREE.label=Marbled Duck
bird.SERIN_CINI.label=European Serin
bird.SITTELLE_TORCHEPOT.label=Eurasian Nuthatch
bird.SIZERIN_FLAMME.label=Redpoll
bird.SPATULE_BLANCHE.label=Eurasian Spoonbill
bird.STERNE_ARCTIQUE.label=Arctic Tern
bird.STERNE_CASPIENNE.label=Caspian Tern
bird.STERNE_CAUGEK.label=Sandwich Tern
bird.STERNE_DE_DOUGALL.label=Roseate Tern
bird.STERNE_HANSEL.label=Gull-billed Tern
bird.STERNE_NAINE.label=Little Tern
bird.STERNE_PIERREGARIN.label=Common Tern
bird.STERNE_VOYAGEUSE.label=Lesser Crested Tern
bird.SYRRHAPTE_PARADOXAL.label=Pallas's Sandgrouse
bird.TADORNE_CASARCA.label=Ruddy Shelduck
bird.TADORNE_DE_BELON.label=Common Shelduck
bird.TALEVE_SULTANE.label=Western Swamphen
bird.TALEVE_VIOLACEE.label=Purple Gallinule
bird.TARIER_DES_PRES.label=Whinchat
bird.TARIER_PATRE.label=European Stonechat
bird.TARIN_DES_AULNES.label=Eurasian Siskin
bird.TETRAS_LYRE.label=Black Grouse
bird.TICHODROME_ECHELETTE.label=Wallcreeper
bird.TORCOL_FOURMILIER.label=Eurasian Wryneck
bird.TOURNEPIERRE_A_COLLIER.label=Ruddy Turnstone
bird.TOURTERELLE_DES_BOIS.label=European Turtle Dove
bird.TOURTERELLE_TURQUE.label=Eurasian Collared Dove
bird.TRAQUET_DU_DESERT.label=Desert Wheatear
bird.TRAQUET_MOTTEUX.label=Northern Wheatear
bird.TRAQUET_OREILLARD.label=Western Black-eared Wheatear
bird.TROGLODYTE_MIGNON.label=Eurasian Wren
bird.VANNEAU_HUPPE.label=Northern Lapwing
bird.VANNEAU_SOCIABLE.label=Sociable Lapwing
bird.VAUTOUR_FAUVE.label=Griffon Vulture
bird.VAUTOUR_MOINE.label=Cinereous Vulture
bird.VAUTOUR_PERCNOPTERE.label=Egyptian Vulture
bird.VENTURON_MONTAGNARD.label=Citril Finch
bird.VERDIER_D_EUROPE.label=European Greenfinch

View File

@@ -424,3 +424,5 @@ bird.VAUTOUR_MOINE.label=Vautour moine
bird.VAUTOUR_PERCNOPTERE.label=Percnoptère d'Égypte
bird.VENTURON_MONTAGNARD.label=Venturon montagnard
bird.VERDIER_D_EUROPE.label=Verdier d'Europe
bird.MESANGE_DES_SAULES.label=Mésange des saules
bird.MESANGE_ALPESTRE.label=Mésange alpestre

View File

@@ -1,5 +0,0 @@
practice.type.picture_exact.label=Image
practice.type.picture_multichoice.label=Image (multiple choice)
practice.type.sound_exact.label=Sound
practice.type.sound_multichoice.label=Sound (multiple choice)
practice.type.sound_multichoice_picture.label=Sound (multiple picture choice)

View File

@@ -1,5 +0,0 @@
practice.type.picture_exact.label=Image
practice.type.picture_multichoice.label=Image (multiple choice)
practice.type.sound_exact.label=Sound
practice.type.sound_multichoice.label=Sound (multiple choice)
practice.type.sound_multichoice_picture.label=Sound (multiple picture choice)

View File

@@ -1,5 +0,0 @@
practice.type.picture_exact.label=Image
practice.type.picture_multichoice.label=Image (choix multiples)
practice.type.sound_exact.label=Son
practice.type.sound_multichoice.label=Son (choix multiples)
practice.type.sound_multichoice_picture.label=Son (choix multiple d'images)

View File

@@ -0,0 +1,5 @@
question.type.picture_exact.label=Image
question.type.picture_multichoice.label=Image (multiple choice)
question.type.sound_exact.label=Sound
question.type.sound_multichoice.label=Sound (multiple choice)
question.type.sound_multichoice_picture.label=Sound (multiple picture choice)

View File

@@ -0,0 +1,5 @@
question.type.picture_exact.label=Image
question.type.picture_multichoice.label=Image (multiple choice)
question.type.sound_exact.label=Sound
question.type.sound_multichoice.label=Sound (multiple choice)
question.type.sound_multichoice_picture.label=Sound (multiple picture choice)

View File

@@ -0,0 +1,5 @@
question.type.picture_exact.label=Image
question.type.picture_multichoice.label=Image (choix multiples)
question.type.sound_exact.label=Son
question.type.sound_multichoice.label=Son (choix multiples)
question.type.sound_multichoice_picture.label=Son (choix multiple d'images)