← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12440: Database translations, implemented automatic fallback according to locale/resource bundle rules. ...

 

------------------------------------------------------------
revno: 12440
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Sun 2013-10-06 09:45:05 +0200
message:
  Database translations, implemented automatic fallback according to locale/resource bundle rules. E.g. when using en_UK it will also search for en and use those translations when the most specific ones are not found.
added:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator/
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator/TranslationLocaleSpecificityComparator.java
  dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/LocaleUtilsTest.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/i18n/locale/I18nLocale.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/Translation.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nLocaleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/I18nLocaleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/DefaultTranslationService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/hibernate/HibernateTranslationStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/LocaleUtils.java
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/security/login.js
  dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/i18n/action/GetStringsFromLocaleAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-dataadmin/src/main/resources/struts.xml


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/i18n/locale/I18nLocale.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/i18n/locale/I18nLocale.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/i18n/locale/I18nLocale.java	2013-10-06 07:45:05 +0000
@@ -32,9 +32,7 @@
 
 public class I18nLocale
     extends BaseIdentifiableObject
-{
-    private String name;
-    
+{    
     private String locale;
 
     // -------------------------------------------------------------------------
@@ -43,7 +41,7 @@
 
     public I18nLocale()
     {
-        this.name = "English, United Kingdom";
+        this.name = "English (United Kingdom)";
         this.locale = "en_GB";
     }
 
@@ -57,16 +55,6 @@
     // Getters and setters
     // -------------------------------------------------------------------------
 
-    public String getName()
-    {
-        return name;
-    }
-
-    public void setName( String name )
-    {
-        this.name = name;
-    }
-
     public String getLocale()
     {
         return locale;

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/Translation.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/Translation.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/Translation.java	2013-10-06 07:45:05 +0000
@@ -69,6 +69,15 @@
     }
 
     // -------------------------------------------------------------------------
+    // Logic
+    // -------------------------------------------------------------------------
+
+    public String getClassIdPropKey()
+    {
+        return className + "-" + id + "-" + property;
+    }
+    
+    // -------------------------------------------------------------------------
     // Getters and setters
     // -------------------------------------------------------------------------
 

=== added directory 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator'
=== added file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator/TranslationLocaleSpecificityComparator.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator/TranslationLocaleSpecificityComparator.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/translation/comparator/TranslationLocaleSpecificityComparator.java	2013-10-06 07:45:05 +0000
@@ -0,0 +1,54 @@
+package org.hisp.dhis.translation.comparator;
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.Comparator;
+
+import org.hisp.dhis.translation.Translation;
+
+/**
+ * Compares two Translation objects based on how specific the Locales are. The
+ * Translation with least specific Locale appears first.
+ * 
+ * @author Lars Helge Overland
+ */
+public class TranslationLocaleSpecificityComparator
+    implements Comparator<Translation>
+{
+    public static final TranslationLocaleSpecificityComparator INSTANCE = new TranslationLocaleSpecificityComparator();
+    
+    @Override
+    public int compare( Translation t1, Translation t2 )
+    {
+        Integer l1 = Integer.valueOf( t1.getLocale().length() );
+        Integer l2 = Integer.valueOf( t2.getLocale().length() );
+        
+        return l1.compareTo( l2 );
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nLocaleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nLocaleService.java	2013-10-04 16:25:27 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nLocaleService.java	2013-10-06 07:45:05 +0000
@@ -42,17 +42,15 @@
 import org.hisp.dhis.common.GenericIdentifiableObjectStore;
 import org.hisp.dhis.common.IdentifiableObject;
 import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator;
+import org.hisp.dhis.common.comparator.LocaleNameComparator;
 import org.hisp.dhis.i18n.locale.I18nLocale;
 import org.hisp.dhis.system.util.LocaleUtils;
-import org.hisp.dhis.system.util.TextUtils;
 import org.springframework.transaction.annotation.Transactional;
 
 @Transactional
 public class DefaultI18nLocaleService
     implements I18nLocaleService
-{
-    private static final String NAME_SEP = ", ";
-    
+{    
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
@@ -132,7 +130,7 @@
         
         String loc = LocaleUtils.getLocaleString( language, country, null );
         
-        String name = languageName + ( countryName != null ? ( NAME_SEP + countryName ) : TextUtils.EMPTY );
+        String name = new Locale( language, country ).toString();
         
         I18nLocale locale = new I18nLocale( name, loc );
         
@@ -180,4 +178,18 @@
     {
         return localeStore.getAllLikeNameOrderedName( name, first, max );
     }
+    
+    public List<Locale> getAllLocales()
+    {
+        List<Locale> locales = new ArrayList<Locale>();
+        
+        for ( I18nLocale locale : localeStore.getAll() )
+        {
+            locales.add( LocaleUtils.getLocale( locale.getLocale() ) );
+        }
+        
+        Collections.sort( locales, LocaleNameComparator.INSTANCE );
+        
+        return locales;
+    }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nService.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/DefaultI18nService.java	2013-10-06 07:45:05 +0000
@@ -37,7 +37,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
@@ -46,9 +45,7 @@
 
 import org.hisp.dhis.common.IdentifiableObject;
 import org.hisp.dhis.common.NameableObject;
-import org.hisp.dhis.common.comparator.LocaleNameComparator;
 import org.hisp.dhis.dataelement.DataElement;
-import org.hisp.dhis.system.util.LocaleUtils;
 import org.hisp.dhis.translation.Translation;
 import org.hisp.dhis.translation.TranslationService;
 import org.hisp.dhis.user.UserSettingService;
@@ -69,6 +66,13 @@
     {
         this.translationService = translationService;
     }
+    
+    private I18nLocaleService localeService;
+
+    public void setLocaleService( I18nLocaleService localeService )
+    {
+        this.localeService = localeService;
+    }
 
     private UserSettingService userSettingService;
 
@@ -78,27 +82,6 @@
     }
     
     // -------------------------------------------------------------------------
-    // Properties
-    // -------------------------------------------------------------------------
-
-    private List<Locale> locales = new ArrayList<Locale>();
-
-    public void setLocales( List<String> localeStrings )
-    {
-        for ( String string : localeStrings )
-        {
-            Locale locale = LocaleUtils.getLocale( string );
-
-            if ( locale != null )
-            {
-                locales.add( locale );
-            }
-        }
-
-        Collections.sort( locales, LocaleNameComparator.INSTANCE );
-    }
-
-    // -------------------------------------------------------------------------
     // Internationalise
     // -------------------------------------------------------------------------
 
@@ -289,7 +272,7 @@
 
     public List<Locale> getAvailableLocales()
     {
-        return locales;
+        return localeService.getAllLocales();
     }
 
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/I18nLocaleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/I18nLocaleService.java	2013-10-04 15:05:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/i18n/I18nLocaleService.java	2013-10-06 07:45:05 +0000
@@ -28,6 +28,8 @@
  */
 
 import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import org.hisp.dhis.i18n.locale.I18nLocale;
@@ -61,4 +63,6 @@
     Collection<I18nLocale> getI18nLocalesBetween( int first, int max );
     
     Collection<I18nLocale> getI18nLocalesBetweenLikeName( String name, int first, int max );
+    
+    List<Locale> getAllLocales();
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/DefaultTranslationService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/DefaultTranslationService.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/DefaultTranslationService.java	2013-10-06 07:45:05 +0000
@@ -31,9 +31,6 @@
 import java.util.Collection;
 import java.util.Locale;
 
-import org.hisp.dhis.translation.Translation;
-import org.hisp.dhis.translation.TranslationService;
-import org.hisp.dhis.translation.TranslationStore;
 import org.springframework.transaction.annotation.Transactional;
 
 /**

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/hibernate/HibernateTranslationStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/hibernate/HibernateTranslationStore.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/translation/hibernate/HibernateTranslationStore.java	2013-10-06 07:45:05 +0000
@@ -37,6 +37,7 @@
 import org.hibernate.Session;
 import org.hibernate.SessionFactory;
 import org.hibernate.criterion.Restrictions;
+import org.hisp.dhis.system.util.LocaleUtils;
 import org.hisp.dhis.translation.Translation;
 import org.hisp.dhis.translation.TranslationStore;
 
@@ -75,6 +76,7 @@
         session.update( translation );
     }
 
+    @SuppressWarnings( "unchecked" )
     public Translation getTranslation( String className, int id, Locale locale, String property )
     {
         Session session = sessionFactory.getCurrentSession();
@@ -83,12 +85,14 @@
 
         criteria.add( Restrictions.eq( "className", className ) );
         criteria.add( Restrictions.eq( "id", id ) );
-        criteria.add( Restrictions.eq( "locale", locale.toString() ) );
+        criteria.add( Restrictions.in( "locale", LocaleUtils.getLocaleFallbacks( locale ) ) );
         criteria.add( Restrictions.eq( "property", property ) );
 
         criteria.setCacheable( true );
 
-        return (Translation) criteria.uniqueResult();
+        List<Translation> translations = LocaleUtils.getTranslationsHighestSpecifity( criteria.list() );
+        
+        return !translations.isEmpty() ? translations.get( 0 ) : null;
     }
 
     @SuppressWarnings( "unchecked" )
@@ -100,11 +104,13 @@
 
         criteria.add( Restrictions.eq( "className", className ) );
         criteria.add( Restrictions.eq( "id", id ) );
-        criteria.add( Restrictions.eq( "locale", locale.toString() ) );
+        criteria.add( Restrictions.in( "locale", LocaleUtils.getLocaleFallbacks( locale ) ) );
 
         criteria.setCacheable( true );
 
-        return criteria.list();
+        List<Translation> translations = criteria.list();
+        
+        return LocaleUtils.getTranslationsHighestSpecifity( translations );
     }
 
     @SuppressWarnings( "unchecked" )
@@ -115,11 +121,13 @@
         Criteria criteria = session.createCriteria( Translation.class );
 
         criteria.add( Restrictions.eq( "className", className ) );
-        criteria.add( Restrictions.eq( "locale", locale.toString() ) );
+        criteria.add( Restrictions.in( "locale", LocaleUtils.getLocaleFallbacks( locale ) ) );
 
         criteria.setCacheable( true );
 
-        return criteria.list();
+        List<Translation> translations = criteria.list();
+        
+        return LocaleUtils.getTranslationsHighestSpecifity( translations );
     }
 
     @SuppressWarnings( "unchecked" )

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-04 16:25:27 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-06 07:45:05 +0000
@@ -794,39 +794,8 @@
 
   <bean id="org.hisp.dhis.i18n.I18nService" class="org.hisp.dhis.i18n.DefaultI18nService">
     <property name="translationService" ref="org.hisp.dhis.translation.TranslationService" />
+    <property name="localeService" ref="org.hisp.dhis.i18n.118nLocaleService" />
     <property name="userSettingService" ref="org.hisp.dhis.user.UserSettingService" />
-    <property name="locales">
-      <list>
-        <value>af</value>
-        <value>ar</value>
-        <value>bi</value>
-        <value>am</value>
-        <value>de</value>
-        <value>dz</value>
-        <value>en</value>
-        <value>es</value>
-        <value>fa</value>
-        <value>fr</value>
-        <value>gu</value>
-        <value>hi</value>
-        <value>id</value>
-        <value>it</value>
-        <value>km</value>
-        <value>lo</value>
-        <value>my</value>
-        <value>ne</value>
-        <value>nl</value>
-        <value>no</value>
-        <value>ps</value>
-        <value>pt</value>
-        <value>ru</value>
-        <value>rw</value>
-        <value>sw</value>
-        <value>tg</value>
-        <value>vi</value>
-        <value>zh</value>
-      </list>
-    </property>
   </bean>
   
   <bean id="org.hisp.dhis.i18n.118nLocaleService" class="org.hisp.dhis.i18n.DefaultI18nLocaleService">

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/LocaleUtils.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/LocaleUtils.java	2013-10-05 14:04:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/LocaleUtils.java	2013-10-06 07:45:05 +0000
@@ -28,7 +28,16 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+
+import org.hisp.dhis.translation.Translation;
+import org.hisp.dhis.translation.comparator.TranslationLocaleSpecificityComparator;
 
 /**
  * @author Oyvind Brucker
@@ -49,7 +58,7 @@
     }
         
     /**
-     * Createa a locale string based on the given language, country and varient.
+     * Createa a locale string based on the given language, country and variant.
      * 
      * @param language the language, cannot be null.
      * @param country the country, can be null.
@@ -77,4 +86,54 @@
         
         return locale;
     }
+    
+    /**
+     * Creates a list of locales of all possible specifities based on the given
+     * Locale. As an example, for the given locale "en_UK", the locales "en" and
+     * "en_UK" are returned.
+     * 
+     * @param locale the Locale.
+     * @return a list of locale strings.
+     */
+    public static List<String> getLocaleFallbacks( Locale locale )
+    {
+        List<String> locales = new ArrayList<String>();
+        
+        locales.add( locale.getLanguage() );
+        
+        if ( !locale.getCountry().isEmpty() )
+        {
+            locales.add( locale.getLanguage() + SEP + locale.getCountry() );
+        }
+        
+        if ( !locale.getVariant().isEmpty() )
+        {
+            locales.add( locale.toString() );
+        }
+        
+        return locales;
+    }
+    
+    /**
+     * Filters the given list of translations in a way where only the most specific
+     * locales are kept for every base locale.
+     * 
+     * @param translations the list of translations.
+     * @return a list of translations.
+     */
+    public static List<Translation> getTranslationsHighestSpecifity( Collection<Translation> translations )
+    {
+        Map<String, Translation> translationMap = new HashMap<String, Translation>();
+        
+        List<Translation> trans = new ArrayList<Translation>( translations );
+        
+        Collections.sort( trans, TranslationLocaleSpecificityComparator.INSTANCE );
+        
+        for ( Translation tr : trans )
+        {
+            translationMap.put( tr.getClassIdPropKey(), tr );
+        }
+        
+        return new ArrayList<Translation>( translationMap.values() );
+    }
 }

=== added file 'dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/LocaleUtilsTest.java'
--- dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/LocaleUtilsTest.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/LocaleUtilsTest.java	2013-10-06 07:45:05 +0000
@@ -0,0 +1,89 @@
+package org.hisp.dhis.system.util;
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.translation.Translation;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class LocaleUtilsTest
+{
+    @Test
+    public void testGetTranslationsHighestSpecifity()
+    {
+        Locale l1 = new Locale( "en", "UK", "en" );
+        Locale l2 = new Locale( "en", "UK" );
+        Locale l3 = new Locale( "en" );
+        
+        Translation t1 = new Translation( DataElement.class.getSimpleName(), 1, l1.toString(), "name", "Name" );
+        Translation t2 = new Translation( DataElement.class.getSimpleName(), 1, l2.toString(), "name", "Name" );
+        Translation t3 = new Translation( DataElement.class.getSimpleName(), 1, l3.toString(), "name", "Name" );
+
+        Translation t4 = new Translation( DataElement.class.getSimpleName(), 1, l1.toString(), "shortName", "Short name" );
+        Translation t5 = new Translation( DataElement.class.getSimpleName(), 1, l2.toString(), "shortName", "Short name" );
+        
+        Translation t6 = new Translation( DataElement.class.getSimpleName(), 1, l2.toString(), "code", "Code" );
+        
+        List<Translation> list = Arrays.asList( t1, t2, t3, t4, t5, t6 );
+        
+        List<Translation> translations = LocaleUtils.getTranslationsHighestSpecifity( list );
+        
+        assertEquals( 3, translations.size() );
+        assertTrue( translations.contains( t1 ) );
+        assertTrue( translations.contains( t4 ) );
+        assertTrue( translations.contains( t6 ) );
+    }
+
+    @Test
+    public void testGetLocaleFallbacks()
+    {
+        Locale l1 = new Locale( "en", "UK", "en" );
+        Locale l2 = new Locale( "en", "UK" );
+        Locale l3 = new Locale( "en" );
+        
+        List<String> locales = LocaleUtils.getLocaleFallbacks( l1 );
+        
+        assertEquals( 3, locales.size() );
+        assertTrue( locales.contains( "en_UK_en" ) );
+        assertTrue( locales.contains( "en_UK" ) );
+        assertTrue( locales.contains( "en_UK" ) );
+        
+        assertEquals( 2, LocaleUtils.getLocaleFallbacks( l2 ).size() );
+        assertEquals( 1, LocaleUtils.getLocaleFallbacks( l3 ).size() );        
+    }
+}

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/security/login.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/security/login.js	2013-10-05 17:11:35 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/security/login.js	2013-10-06 07:45:05 +0000
@@ -16,7 +16,7 @@
     
     var locale = $.cookie( login.localeCookie );
     
-    if ( undefined !== locale )
+    if ( undefined !== locale && locale != null )
     {
     	login.changeLocale( locale );
     	$( '#localeSelect option[value="' + locale + '"]' ).attr( 'selected', 'selected' );

=== modified file 'dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/i18n/action/GetStringsFromLocaleAction.java'
--- dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/i18n/action/GetStringsFromLocaleAction.java	2013-10-04 17:06:16 +0000
+++ dhis-2/dhis-web/dhis-web-commons/src/main/java/org/hisp/dhis/i18n/action/GetStringsFromLocaleAction.java	2013-10-06 07:45:05 +0000
@@ -66,10 +66,10 @@
     public String execute()
         throws Exception
     {
-        Locale locale = LocaleUtils.getLocale( loc );
-
         if ( loc != null )
-        {        
+        {
+            Locale locale = LocaleUtils.getLocale( loc );
+    
             i18nObject = manager.getI18n( this.getClass(), locale );
         }
         

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-dataadmin/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-dataadmin/src/main/resources/struts.xml	2013-10-04 14:34:08 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-dataadmin/src/main/resources/struts.xml	2013-10-06 07:45:05 +0000
@@ -58,7 +58,6 @@
       <result name="success" type="velocity">/main.vm</result>
       <param name="page">/dhis-web-maintenance-dataadmin/localeList.vm</param>
       <param name="menu">/dhis-web-maintenance-dataadmin/menu.vm</param>
-      <param name="requiredAuthorities">F_LOCALE_MANAGEMENT</param>
     </action>
 
     <action name="getLocale"
@@ -100,14 +99,14 @@
       <result name="success" type="velocity">/main.vm</result>
       <param name="page">/dhis-web-maintenance-dataadmin/updateLocaleForm.vm</param>
       <param name="menu">/dhis-web-maintenance-dataadmin/menu.vm</param>
-      <param name="requiredAuthorities">F_LOCALE_UPDATE</param>
+      <param name="requiredAuthorities">F_LOCALE_ADD</param>
     </action>
 
     <action name="updateLocale"
       class="org.hisp.dhis.dataadmin.action.locale.UpdateLocaleAction">
       <result name="success" type="redirect">locale.action
       </result>
-      <param name="requiredAuthorities">F_LOCALE_UPDATE</param>
+      <param name="requiredAuthorities">F_LOCALE_ADD</param>
     </action>
 
     <action name="validateLocale" class="org.hisp.dhis.dataadmin.action.locale.ValidateLocaleAction">