Skip to content

Commit

Permalink
Add some localisation
Browse files Browse the repository at this point in the history
Implemented with a big bad singleton. It features namespaces, though.
  • Loading branch information
Botffy committed Dec 9, 2016
1 parent d40ef14 commit fbb7233
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 21 deletions.
20 changes: 13 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
compile group: 'com.github.stefanbirkner', name: 'fishbowl', version: '1.4.0'
compile group: 'net.sourceforge.argparse4j', name: 'argparse4j', version: '0.7.0'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5'
compile group: 'com.google.guava', name: 'guava', version: '20.0'

compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
runtime group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.21'
Expand Down Expand Up @@ -72,13 +73,18 @@ javadoc {
}

import org.apache.tools.ant.filters.ReplaceTokens
processResources {
filter(ReplaceTokens, tokens: [
"version": version,
"built.at": (new Date()).format("yyyy-MM-dd HH:mm:ssZ"),
"built.by": System.getProperty('user.name'),
"build.JDK": System.getProperty('java.version')
])
import org.apache.tools.ant.filters.EscapeUnicode
tasks.withType(ProcessResources).each { task ->
task.from(task.getSource()) {
include '**/*.properties'
filter(EscapeUnicode)
filter(ReplaceTokens, tokens: [
"version": version,
"built.at": (new Date()).format("yyyy-MM-dd HH:mm:ssZ"),
"built.by": System.getProperty('user.name'),
"build.JDK": System.getProperty('java.version')
])
}
}

run {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/ppke/itk/xplang/common/Messages.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ppke.itk.xplang.common;

/**
* A big bad singleton encapsulating a resource bundle.
*/
public class Messages {
}
55 changes: 55 additions & 0 deletions src/main/java/ppke/itk/xplang/common/Translator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ppke.itk.xplang.common;

import java.util.*;

import static java.util.Arrays.asList;

public final class Translator {
private final static Set<String> LANGUAGES = new HashSet<>(asList("", "hu", "en"));
private final static String DEFAULT_LANGUAGE = "";

private static final Map<String, Translator> instances = new HashMap<>();
private static String language = DEFAULT_LANGUAGE;

private ResourceBundle messages;
private Translator(String namespace) {
reset(namespace);
}

private void reset(String namespace) {
Locale locale = new Locale(language);
messages = ResourceBundle.getBundle(
String.format("messages.%s", namespace),
locale,
ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_PROPERTIES)
);
}

public String translate(String key) {
return messages.getString(key);
}

public String translate(String key, Object... args) {
return String.format(messages.getString(key), args);
}

public static Translator getInstance(String namespace) {
namespace = namespace.toLowerCase();
if(!instances.containsKey(namespace)) {
instances.put(namespace, new Translator(namespace));
}

return instances.get(namespace);
}

public static void setLanguage(String newLanguage) {
if(!LANGUAGES.contains(newLanguage)) {
throw new IllegalStateException(String.format("Language '%s' is not supported", language));
}
language = newLanguage;

for(Map.Entry<String, Translator> instance : instances.entrySet()) {
instance.getValue().reset(instance.getKey());
}
}
}
17 changes: 12 additions & 5 deletions src/main/java/ppke/itk/xplang/lang/PlangGrammar.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ppke.itk.xplang.ast.*;
import ppke.itk.xplang.common.Translator;
import ppke.itk.xplang.parser.*;

import java.util.ArrayList;
Expand All @@ -11,6 +12,7 @@
import java.util.stream.Stream;

public class PlangGrammar extends Grammar {
private final static Translator translator = Translator.getInstance("Plang");
private final static Logger log = LoggerFactory.getLogger("Root.Parser.Grammar");

public PlangGrammar() {
Expand Down Expand Up @@ -60,8 +62,10 @@ public void setup(Context ctx) {
*/
protected Program program(Parser parser) throws ParseError {
log.debug("Program");
parser.accept("PROGRAM", "A programnak a PROGRAM kulcsszóval kell keződnie!");
Token nameToken = parser.accept("IDENTIFIER", "Hiányzik a program neve (egy azonosító).");
parser.accept("PROGRAM",
translator.translate("plang.program_keyword_missing", "PROGRAM"));
Token nameToken = parser.accept("IDENTIFIER",
translator.translate("plang.missing_program_name"));

if(parser.actual().symbol().equals(parser.context().lookup("DECLARE"))) {
declarations(parser);
Expand All @@ -80,7 +84,8 @@ protected Program program(Parser parser) throws ParseError {
}
} while(!stoppers.contains(parser.actual().symbol()));

parser.accept("END_PROGRAM", "A programot a PROGRAM_VÉGE kulcsszóval kell lezárni.");
parser.accept("END_PROGRAM",
translator.translate("plang.missing_end_program", "PROGRAM_VÉGE"));
Scope scope = parser.context().closeScope();
Sequence sequence = new Sequence(statementList);

Expand All @@ -92,8 +97,10 @@ protected Program program(Parser parser) throws ParseError {
*/
protected void declarations(Parser parser) throws ParseError {
log.debug("Declarations");
parser.accept("DECLARE", "Hiányzik a VÁLTOZÓK kulcsszó");
parser.accept("COLON", "Hiányzik a VÁLTOZÓK kulcsszó után a kettőspont.");
parser.accept("DECLARE",
translator.translate("plang.missing_declarations_keyword", "VÁLTOZÓK"));
parser.accept("COLON",
translator.translate("plang.missing_colon_after_declarations_keyword", "VÁLTOZÓK"));

variableDeclaration(parser);
while(parser.actual().symbol().equals(parser.context().lookup("COMMA"))) {
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/ppke/itk/xplang/parser/LexerError.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package ppke.itk.xplang.parser;

import ppke.itk.xplang.common.Translator;

/**
* An error of the {@link Lexer}. Thrown when the Lexer encounters a piece of text it cannot match to any of the
* {@link Symbol}s it knows.
*/
public class LexerError extends ParseError {
private final static Translator translator = Translator.getInstance("parser");

/**
* Signal a lexing error.
* @param token Lexer is supposed to return the rest of the line, starting from the point of error.
*/
LexerError(Token token) {
super(String.format("Could not tokenize '%s'", token.lexeme()), token.location());
super(translator.translate("parser.LexerError.message", token.lexeme()), token.location());
}
}
6 changes: 4 additions & 2 deletions src/main/java/ppke/itk/xplang/parser/NameClashError.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package ppke.itk.xplang.parser;

import ppke.itk.xplang.common.Translator;

/**
* Thrown when you try to declare something by a name, but that name is already taken in that scope.
*/
public class NameClashError extends SemanticError {
private final static String NAME_CLASH_MESSAGE = "Could not declare '%s': name already taken.";
private final static Translator translator = Translator.getInstance("parser");

NameClashError(Token token) {
this(NAME_CLASH_MESSAGE, token);
this(translator.translate("parser.NameClashError.message"), token);
}

NameClashError(String message, Token token) {
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/ppke/itk/xplang/parser/NameError.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package ppke.itk.xplang.parser;

import ppke.itk.xplang.common.Translator;

public class NameError extends SemanticError {
private final static String NAME_ERROR_MESSAGE = "'%s' does not exist.";
private final static Translator translator = Translator.getInstance("parser");

NameError(Token token) {
this(NAME_ERROR_MESSAGE, token);
this(translator.translate("parser.NameError.message"), token);
}

NameError(String message, Token token) {
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/ppke/itk/xplang/parser/SyntaxError.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package ppke.itk.xplang.parser;


import ppke.itk.xplang.common.Translator;

import java.util.Collection;
import java.util.Collections;

/**
* Thrown when the source code does not conform the expected structure of the language.
*/
public class SyntaxError extends ParseError {
private static final String EXPECT_MANY_MSG = "Expected any symbol of %s, encountered %s";
private static final String EXPECT_ONE_MSG = "Expected symbol %s, encountered %s";
private final static Translator translator = Translator.getInstance("parser");

/**
* Signal a syntax error with the given message.
Expand All @@ -33,7 +34,10 @@ public SyntaxError(String message, Collection<Symbol> expected, Symbol actual, T
* @param token The token causing the error.
*/
public SyntaxError(Collection<Symbol> expected, Symbol actual, Token token) {
this(expected.size() > 1? EXPECT_MANY_MSG : EXPECT_ONE_MSG, expected, actual, token);
this(translator.translate(expected.size() > 1?
"parser.SyntaxError.message.expectMany" :
"parser.SyntaxError.message.expectOne"
), expected, actual, token);
}

/**
Expand All @@ -43,7 +47,7 @@ public SyntaxError(Collection<Symbol> expected, Symbol actual, Token token) {
* @param token The token causing the error.
*/
public SyntaxError(Symbol expected, Symbol actual, Token token) {
this(EXPECT_ONE_MSG, expected, actual, token);
this(translator.translate("parser.SyntaxError.message.expectOne"), expected, actual, token);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/messages/parser.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parser.NameClashError.message=Could not declare '%s': name already taken.
parser.LexerError.message=Could not tokenize '%s'
parser.NameError.message='%s' does not exist.
parser.SyntaxError.message.expectMany=Expected any symbol of %s, encountered %s
parser.SyntaxError.message.expectOne=Expected symbol %s, encountered %s

6 changes: 6 additions & 0 deletions src/main/resources/messages/parser_hu.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parser.NameClashError.message=A '%s' név már használatban van.
parser.LexerError.message=Ismeretlen szimbólum: '%s'
parser.NameError.message='%s' nevű objektum nem létezik.
parser.SyntaxError.message.expectMany=A következő szimbólumok valamelyikére számítottam: %s, helyette azt kaptam, hogy %s
parser.SyntaxError.message.expectOne=Arra a szimbólura számítottam, hogy %s, de azt kaptam, hogy %s

5 changes: 5 additions & 0 deletions src/main/resources/messages/plang.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plang.program_keyword_missing=The program must start with the %s keyword
plang.missing_program_name=The name of the program (an identifier) is missing.
plang.missing_end_program=The program must end with the %s keyword
plang.missing_declarations_keyword=The %s keyword is missing
plang.missing_colon_after_declarations_keyword=Missing colon after the %s keyword
5 changes: 5 additions & 0 deletions src/main/resources/messages/plang_hu.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plang.program_keyword_missing=A programnak a %s kulcsszóval kell keződnie!
plang.missing_program_name=Hiányzik a program neve (egy azonosító).
plang.missing_end_program=A programot a %s kulcsszóval kell lezárni.
plang.missing_declarations_keyword=Hiányzik a %s kulcsszó
plang.missing_colon_after_declarations_keyword=Hiányzik a %s kulcsszó után a kettőspont.
28 changes: 28 additions & 0 deletions src/test/java/ppke/itk/xplang/common/TranslatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ppke.itk.xplang.common;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class TranslatorTest {
@Test public void translatorShouldParametrizeMessage() {
Translator.setLanguage("");
Translator translator = Translator.getInstance("test");
assertEquals("TEST 1", translator.translate("parser.parametrized.message", 1));
}

@Test public void translatorShouldFallBackToDefault() {
Translator.setLanguage("hu");
Translator translator = Translator.getInstance("test");

assertEquals("TEST_FALLBACK", translator.translate("parser.missing.message"));
}

@Test public void switchingLanguagesShouldChangeInstances() {
Translator.setLanguage("");
Translator translator = Translator.getInstance("test");

Translator.setLanguage("hu");
assertEquals("DIFO_HU", translator.translate("parser.simple.message"));
}
}
3 changes: 3 additions & 0 deletions src/test/resources/messages/test.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parser.simple.message=DIFO
parser.parametrized.message=TEST %s
parser.missing.message=TEST_FALLBACK
2 changes: 2 additions & 0 deletions src/test/resources/messages/test_hu.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parser.simple.message=DIFO_HU
parser.parametrized.message=TEST HU %s

0 comments on commit fbb7233

Please sign in to comment.