Improved I18N support:

* GamePageData now includes the selected language's code.
* Added support for multiple fetches in one call to the Translator
service.
This commit is contained in:
Emmanuel BENOîT 2012-04-05 11:25:08 +02:00
parent c7949e41cc
commit 9a7bc03171
9 changed files with 316 additions and 50 deletions

View file

@ -239,6 +239,20 @@ class I18NData
} }
/**
* Access the store for some language
*
* @param language
* the language to access
*
* @return the language store, or <code>null</code> if the language is not defined.
*/
LanguageStore getStore( String language )
{
return this.languages.get( language );
}
/** /**
* Sets or creates the translation for a given language/string identifier pair. * Sets or creates the translation for a given language/string identifier pair.
* *
@ -257,7 +271,8 @@ class I18NData
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the string does not exist * if the string does not exist
*/ */
String setTranslation( final int administrator , final String language , final String string , final String translation ) String setTranslation( final int administrator , final String language , final String string ,
final String translation )
{ {
// Get existing translation // Get existing translation
LanguageStore store = this.languages.get( language ); LanguageStore store = this.languages.get( language );

View file

@ -1,7 +1,10 @@
package com.deepclone.lw.beans.i18n; package com.deepclone.lw.beans.i18n;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -13,10 +16,13 @@ import com.deepclone.lw.interfaces.i18n.UnknownStringException;
/** /**
* Translation component
*
* <p>
* The translator bean's implementation uses the contents of the {@link I18NData} instance, which it * The translator bean's implementation uses the contents of the {@link I18NData} instance, which it
* only accesses in read-only mode. * only accesses in read-only mode.
* *
* @author tseeker * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/ */
public class TranslatorBean public class TranslatorBean
implements Translator implements Translator
@ -83,6 +89,23 @@ public class TranslatorBean
} }
/* Documentation in Translator interface */
@Override
public String getLanguageName( String language )
throws UnknownLanguageException
{
this.data.readLock( ).lock( );
try {
if ( !this.data.isLanguageComplete( language ) ) {
throw new UnknownLanguageException( language );
}
return this.data.getLanguageName( language );
} finally {
this.data.readLock( ).unlock( );
}
}
/* Documentation in Translator interface */ /* Documentation in Translator interface */
@Override @Override
public String translate( String language , String string ) public String translate( String language , String string )
@ -103,17 +126,30 @@ public class TranslatorBean
} }
/* Documentation in Translator interface */ /**
* Access the store for the specified language then extract translations
*/
@Override @Override
public String getLanguageName( String language ) public Map< String , String > translate( String language , Collection< String > strings )
throws UnknownLanguageException throws UnknownStringException , UnknownLanguageException
{ {
this.data.readLock( ).lock( ); this.data.readLock( ).lock( );
try { try {
if ( !this.data.isLanguageComplete( language ) ) { if ( !this.data.isLanguageComplete( language ) ) {
throw new UnknownLanguageException( language ); throw new UnknownLanguageException( language );
} }
return this.data.getLanguageName( language );
LanguageStore store = this.data.getStore( language );
HashMap< String , String > result = new HashMap< String , String >( );
for ( String identifier : strings ) {
String value = store.getTranslation( identifier );
if ( value == null ) {
throw new UnknownStringException( identifier );
}
result.put( identifier , value );
}
return result;
} finally { } finally {
this.data.readLock( ).unlock( ); this.data.readLock( ).unlock( );
} }

View file

@ -79,19 +79,8 @@ public class EmpireDAOBean
public GeneralInformation getInformation( int empireId ) public GeneralInformation getInformation( int empireId )
{ {
String sql = "SELECT * FROM emp.general_information WHERE id = ?"; String sql = "SELECT * FROM emp.general_information WHERE id = ?";
RowMapper< GeneralInformation > mapper = new RowMapper< GeneralInformation >( ) {
@Override
public GeneralInformation mapRow( ResultSet rs , int rowNum )
throws SQLException
{
String st = rs.getString( "status" );
Character status = ( st == null ) ? null : st.charAt( 0 );
return new GeneralInformation( status , rs.getString( "name" ) , rs.getString( "alliance" ) , rs
.getLong( "cash" ) , rs.getLong( "game_time" ) , rs.getInt( "account_id" ) );
}
};
try { try {
return this.dTemplate.queryForObject( sql , mapper , empireId ); return this.dTemplate.queryForObject( sql , new GeneralInformationRowMapper( ) , empireId );
} catch ( EmptyResultDataAccessException e ) { } catch ( EmptyResultDataAccessException e ) {
return null; return null;
} }

View file

@ -117,7 +117,7 @@ public class EmpireManagementBean
return new GamePageData( generalInformation.getName( ) , generalInformation.getStatus( ) , return new GamePageData( generalInformation.getName( ) , generalInformation.getStatus( ) ,
generalInformation.getTag( ) , generalInformation.getCash( ) , generalInformation.getNextTick( ) , generalInformation.getTag( ) , generalInformation.getCash( ) , generalInformation.getNextTick( ) ,
planets , rlTime ); planets , rlTime, generalInformation.getLanguage( ) );
} }

View file

@ -0,0 +1,36 @@
package com.deepclone.lw.beans.empire;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import com.deepclone.lw.sqld.game.GeneralInformation;
class GeneralInformationRowMapper
implements RowMapper< GeneralInformation >
{
@Override
public GeneralInformation mapRow( ResultSet rs , int rowNum )
throws SQLException
{
GeneralInformation info = new GeneralInformation( );
info.setName( rs.getString( "name" ) );
info.setTag( rs.getString( "alliance" ) );
info.setCash( rs.getLong( "cash" ) );
info.setLanguage( rs.getString( "language" ) );
info.setNextTick( rs.getLong( "game_time" ) );
info.setAccountId( rs.getInt( "account_id" ) );
String statusString = rs.getString( "status" );
info.setStatus( rs.wasNull( ) ? null : statusString.charAt( 0 ) );
return info;
}
}

View file

@ -506,6 +506,7 @@ CREATE VIEW emp.enemies
-- General information view -- General information view
-- --
DROP VIEW IF EXISTS emp.general_information CASCADE;
CREATE VIEW emp.general_information CREATE VIEW emp.general_information
AS SELECT e.name_id AS id , en.name AS name , AS SELECT e.name_id AS id , en.name AS name ,
( CASE ( CASE
@ -516,13 +517,17 @@ CREATE VIEW emp.general_information
END ) AS status , END ) AS status ,
e.cash AS cash , a.tag AS alliance , e.cash AS cash , a.tag AS alliance ,
st.next_tick AS game_time , st.next_tick AS game_time ,
av.id AS account_id av.id AS account_id ,
av.language AS language
FROM emp.empires e FROM emp.empires e
INNER JOIN naming.empire_names en ON en.id = e.name_id INNER JOIN naming.empire_names en
INNER JOIN users.accounts_view av ON av.id = en.owner_id ON en.id = e.name_id
INNER JOIN users.accounts_view av
ON av.id = en.owner_id
LEFT OUTER JOIN emp.alliance_members am LEFT OUTER JOIN emp.alliance_members am
ON am.empire_id = e.name_id AND NOT am.is_pending ON am.empire_id = e.name_id AND NOT am.is_pending
LEFT OUTER JOIN emp.alliances a ON a.id = am.alliance_id LEFT OUTER JOIN emp.alliances a
ON a.id = am.alliance_id
CROSS JOIN sys.status st; CROSS JOIN sys.status st;
GRANT SELECT ON emp.general_information TO :dbuser; GRANT SELECT ON emp.general_information TO :dbuser;

View file

@ -3,11 +3,12 @@ package com.deepclone.lw.sqld.game;
public class GeneralInformation public class GeneralInformation
{ {
private String name;
private String language;
private Character status; private Character status;
private String name;
private String tag; private String tag;
private long cash; private long cash;
@ -17,50 +18,87 @@ public class GeneralInformation
private int accountId; private int accountId;
public GeneralInformation( Character status , String name , String tag , long cash , long nextTick , int accountId ) public String getName( )
{
return this.name;
}
public void setName( String name )
{ {
this.status = status;
this.name = name; this.name = name;
this.tag = tag; }
this.cash = cash;
this.nextTick = nextTick;
this.accountId = accountId; public String getLanguage( )
{
return this.language;
}
public void setLanguage( String language )
{
this.language = language;
} }
public Character getStatus( ) public Character getStatus( )
{ {
return status; return this.status;
} }
public String getName( ) public void setStatus( Character status )
{ {
return name; this.status = status;
} }
public String getTag( ) public String getTag( )
{ {
return tag; return this.tag;
}
public void setTag( String tag )
{
this.tag = tag;
} }
public long getCash( ) public long getCash( )
{ {
return cash; return this.cash;
}
public void setCash( long cash )
{
this.cash = cash;
} }
public long getNextTick( ) public long getNextTick( )
{ {
return nextTick; return this.nextTick;
}
public void setNextTick( long nextTick )
{
this.nextTick = nextTick;
} }
public int getAccountId( ) public int getAccountId( )
{ {
return accountId; return this.accountId;
}
public void setAccountId( int accountId )
{
this.accountId = accountId;
} }
} }

View file

@ -1,6 +1,8 @@
package com.deepclone.lw.interfaces.i18n; package com.deepclone.lw.interfaces.i18n;
import java.util.Collection;
import java.util.Map;
import java.util.Set; import java.util.Set;
@ -8,12 +10,12 @@ import java.util.Set;
/** /**
* Translator service interface * Translator service interface
* *
* <p>
* This interface defines the methods available on the Translator service. One such service should * This interface defines the methods available on the Translator service. One such service should
* be present on all nodes. All Translator service instances are managed by the I18NManager service, * be present on all nodes. All Translator service instances are managed by the I18NManager service,
* which is shared between nodes and notifies Translator instances of database updates. * which is shared between nodes and notifies Translator instances of database updates.
* *
* @author tseeker * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*
*/ */
public interface Translator public interface Translator
{ {
@ -62,4 +64,28 @@ public interface Translator
*/ */
public String translate( String language , String string ) public String translate( String language , String string )
throws UnknownStringException , UnknownLanguageException; throws UnknownStringException , UnknownLanguageException;
/**
* Translate multiple strings based on their identifiers
*
* <p>
* This method must be implemented to allow "en masse" string translations. It will fetch the
* translations in a given language for an arbitrary quantity of string identifiers, returning
* the results as a map of identifiers to translations.
*
* @param language
* the identifier of the language to translate to
* @param strings
* a collection of string identifiers
*
* @return a map associating string identifiers to translations
*
* @throws UnknownStringException
* if one of the string identifiers does not match a string
* @throws UnknownLanguageException
* if the language does not exist or is not supported
*/
public Map< String , String > translate( String language , Collection< String > strings )
throws UnknownStringException , UnknownLanguageException;
} }

View file

@ -8,23 +8,91 @@ import java.util.List;
/**
* General information included in a game response
*
* <p>
* This class stores common information which is included in most in-game responses. This
* information includes some essential preferences, the general state of the empire, and some
* information about the server's state.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class GamePageData public class GamePageData
implements Serializable implements Serializable
{ {
/**
* The serialisation version identifier
*
* <ul>
* <li>Introduced in B6M1 with ID 1
* <li>Modified in B6M2, ID set to 2
* </ul>
*/
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** The name of the current empire */
private final String empire; private final String empire;
/**
* State of the account
*
* <p>
* This field indicates the "special" state of the account: <code>q</code> is for accounts that
* are quitting, <code>v</code> for accounts in vacation mode and <code>s</code> for accounts
* that are about to enter vacation mode. <code>null</code> indicates "none of the above".
*/
private final Character special; private final Character special;
/** Alliance tag, or <code>null</code> if not a full member of any alliance */
private final String alliance; private final String alliance;
/**
* Current cash of the empire
*
* <p>
* FIXME: will be replaced with actual resources
*/
private final long cash; private final long cash;
/** Timestamp from the server's system */
private final Date serverTime; private final Date serverTime;
/** Current game time (from last update identifier) */
private final GameTime gameTime; private final GameTime gameTime;
/** Planets owned by the empire */
private final List< NameIdPair > planets; private final List< NameIdPair > planets;
/** Whether the player wants to see "real" times or in-game times */
private final boolean useRLTime; private final boolean useRLTime;
/** Code of selected language */
private final String language;
/**
* Initialise the general game information record
*
* @param empire
* the empire's name
* @param special
* the special account state character (see {@link #special})
* @param alliance
* current alliance tag
* @param cash
* current cash
* @param gameTime
* last update identifier
* @param planets
* list of planets owned by the empire
* @param useRLTime
* whether the player wants to see "real" times or in-game times
* @param language
* selected language code
*/
public GamePageData( String empire , Character special , String alliance , long cash , long gameTime , public GamePageData( String empire , Character special , String alliance , long cash , long gameTime ,
List< NameIdPair > planets , boolean useRLTime ) List< NameIdPair > planets , boolean useRLTime , String language )
{ {
this.empire = empire; this.empire = empire;
this.special = special; this.special = special;
@ -34,54 +102,107 @@ public class GamePageData
this.gameTime = new GameTime( gameTime ); this.gameTime = new GameTime( gameTime );
this.planets = Collections.unmodifiableList( planets ); this.planets = Collections.unmodifiableList( planets );
this.useRLTime = useRLTime; this.useRLTime = useRLTime;
this.language = language;
} }
/**
* Gets the name of the current empire.
*
* @return the name of the current empire
*/
public String getEmpire( ) public String getEmpire( )
{ {
return empire; return this.empire;
} }
/**
* Gets the state of the account
*
* @return the state of the account
*/
public Character getSpecial( ) public Character getSpecial( )
{ {
return special; return this.special;
} }
/**
* Gets the alliance tag
*
* @return the alliance tag, or <code>null</code> if not a full member of any alliance
*/
public String getAlliance( ) public String getAlliance( )
{ {
return alliance; return this.alliance;
} }
/**
* Gets the current cash of the empire
*
* @return the current cash of the empire
*/
public long getCash( ) public long getCash( )
{ {
return cash; return this.cash;
} }
/**
* Gets the timestamp from the server's system.
*
* @return the timestamp from the server's system
*/
public Date getServerTime( ) public Date getServerTime( )
{ {
return serverTime; return this.serverTime;
} }
/**
* Gets the current game time
*
* @return the current game time
*/
public GameTime getGameTime( ) public GameTime getGameTime( )
{ {
return gameTime; return this.gameTime;
} }
/**
* Gets the planets owned by the empire.
*
* @return the list of planets owned by the empire
*/
public List< NameIdPair > getPlanets( ) public List< NameIdPair > getPlanets( )
{ {
return planets; return this.planets;
} }
/**
* Checks if the player wants to see "real" times or in-game times.
*
* @return <code>true</code> if the player wants to see "real" times, <code>false</code> for
* in-game times
*/
public boolean isUseRLTime( ) public boolean isUseRLTime( )
{ {
return useRLTime; return this.useRLTime;
}
/**
* Gets the code of selected language.
*
* @return the code of selected language
*/
public String getLanguage( )
{
return this.language;
} }
} }