Bug 396198 - Enhancement of translation service in tools.services
diff --git a/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF
index 9374d6b..f0ce943 100644
--- a/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF
@@ -17,3 +17,4 @@
 Bundle-ActivationPolicy: lazy
 Import-Package: javax.annotation;version="1.0.0",
  javax.inject;version="1.0.0"
+Bundle-Activator: org.eclipse.e4.tools.services.ToolsServicesActivator
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java
index fc22333..b5512df 100644
--- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java
@@ -1,6 +1,8 @@
 package org.eclipse.e4.tools.services;
 
+import org.eclipse.osgi.service.localization.BundleLocalization;
+
 public interface IMessageFactoryService {
-	public <M> M createInstance(final String locale, final Class<M> messages)
+	public <M> M createInstance(final String locale, final Class<M> messages, BundleLocalization localization)
 			throws InstantiationException, IllegalAccessException;
 }
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java
index d2948f9..05afcf7 100644
--- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java
@@ -1,9 +1,14 @@
 package org.eclipse.e4.tools.services;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
 public @interface Message {
 	public enum ReferenceType {
 		NONE, SOFT, WEAK
 	}
 
 	ReferenceType referenceType() default ReferenceType.SOFT;
+	String contributorURI() default "";
 }
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java
new file mode 100644
index 0000000..04d6367
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java
@@ -0,0 +1,63 @@
+package org.eclipse.e4.tools.services;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+@SuppressWarnings("deprecation")
+public class ToolsServicesActivator implements BundleActivator {
+
+	static private ToolsServicesActivator defaultInstance;
+	private BundleContext bundleContext;
+	private ServiceTracker<PackageAdmin, PackageAdmin> pkgAdminTracker;
+	private ServiceTracker<LogService, LogService> logTracker;
+
+	public ToolsServicesActivator() {
+		defaultInstance = this;
+	}
+
+	public static ToolsServicesActivator getDefault() {
+		return defaultInstance;
+	}
+
+	public void start(BundleContext context) throws Exception {
+		bundleContext = context;
+	}
+
+	public void stop(BundleContext context) throws Exception {
+		if (pkgAdminTracker != null) {
+			pkgAdminTracker.close();
+			pkgAdminTracker = null;
+		}
+		if (logTracker != null) {
+			logTracker.close();
+			logTracker = null;
+		}
+		bundleContext = null;
+	}
+
+	public PackageAdmin getPackageAdmin() {
+		if (pkgAdminTracker == null) {
+			if (bundleContext == null)
+				return null;
+			pkgAdminTracker = new ServiceTracker<PackageAdmin, PackageAdmin>(bundleContext,
+					PackageAdmin.class, null);
+			pkgAdminTracker.open();
+		}
+		return (PackageAdmin) pkgAdminTracker.getService();
+	}
+
+	public LogService getLogService() {
+		if (logTracker == null) {
+			if (bundleContext == null)
+				return null;
+			logTracker = new ServiceTracker<LogService, LogService>(bundleContext,
+					LogService.class, null);
+			logTracker.open();
+		}
+		return logTracker.getService();
+	}
+
+}
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java
index c255ee1..20d0deb 100644
--- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java
@@ -20,6 +20,7 @@
 import java.util.MissingResourceException;
 import java.util.PropertyResourceBundle;
 import java.util.ResourceBundle;
+
 import org.eclipse.osgi.service.localization.BundleLocalization;
 
 /**
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java
index 2d8d349..e09b23b 100644
--- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java
@@ -19,15 +19,25 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
 
 import org.eclipse.e4.tools.services.IMessageFactoryService;
 import org.eclipse.e4.tools.services.Message;
 import org.eclipse.e4.tools.services.Message.ReferenceType;
+import org.eclipse.e4.tools.services.ToolsServicesActivator;
+import org.eclipse.osgi.service.localization.BundleLocalization;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.log.LogService;
 
 public class MessageFactoryServiceImpl implements IMessageFactoryService {
 
+	private static LogService logService = ToolsServicesActivator.getDefault().getLogService();
+
 	// Cache so when multiple instance use the same message class
 	private Map<Object, Reference<Object>> SOFT_CACHE = Collections
 			.synchronizedMap(new HashMap<Object, Reference<Object>>());
@@ -37,9 +47,9 @@
 
 	private int CLEANUPCOUNT = 0;
 
-	public <M> M createInstance(final String locale, final Class<M> messages)
+	public <M> M createInstance(final String locale, final Class<M> messages, final BundleLocalization localization)
 			throws InstantiationException, IllegalAccessException {
-		String key = messages.getName() + "_" + locale;
+		String key = messages.getName() + "_" + locale; //$NON-NLS-1$
 
 		final Message annotation = messages.getAnnotation(Message.class);
 		Map<Object, Reference<Object>> cache = null;
@@ -85,13 +95,13 @@
 		M instance;
 
 		if (System.getSecurityManager() == null) {
-			instance = doCreateInstance(locale, messages, annotation);
+			instance = doCreateInstance(locale, messages, annotation, localization);
 		} else {
 			instance = AccessController.doPrivileged(new PrivilegedAction<M>() {
 
 				public M run() {
 					try {
-						return doCreateInstance(locale, messages, annotation);
+						return doCreateInstance(locale, messages, annotation, localization);
 					} catch (InstantiationException e) {
 						e.printStackTrace();
 					} catch (IllegalAccessException e) {
@@ -115,13 +125,49 @@
 	}
 
 	private static <M> M doCreateInstance(String locale, Class<M> messages,
-			Message annotation) throws InstantiationException,
+			Message annotation, BundleLocalization localization) throws InstantiationException,
 			IllegalAccessException {
 
-		String basename = messages.getName().replace('.', '/');
-		PropertiesBundleTranslationProvider provider = new PropertiesBundleTranslationProvider(
-				messages.getClassLoader(), basename);
-
+		Locale loc = null;
+		try {
+			loc = locale == null ? Locale.getDefault() : ResourceBundleHelper.toLocale(locale);
+		}
+		catch (Exception e) {
+			//parsing the locale String to a Locale failed, so we use the default Locale
+			if (logService != null)
+				logService.log(LogService.LOG_ERROR, "Invalid locale", e); //$NON-NLS-1$
+			loc = Locale.getDefault();
+		}
+		
+		ResourceBundle resourceBundle = null;
+		if (annotation != null && annotation.contributorURI().length() > 0) {
+			resourceBundle = ResourceBundleHelper.getResourceBundleForUri(annotation.contributorURI(), loc, localization);
+		}
+		
+		if (resourceBundle == null) {
+			//check for the resource bundle relative to the messages class
+			String baseName = messages.getName().replace('.', '/');
+			
+			try {
+				resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle(baseName, loc, messages.getClassLoader());
+			}
+			catch (MissingResourceException e) {
+				//do nothing as this just means there is no resource bundle named
+				//like the messages class in the same package
+				//therefore we will go on and search for the OSGi resource bundle
+			}
+		}
+		
+		if (resourceBundle == null) {
+			//retrieve the OSGi resource bundle
+			Bundle bundle = FrameworkUtil.getBundle(messages);
+			resourceBundle = localization.getLocalization(bundle, locale);
+		}
+		
+		//always create a provider, if there is no resource bundle found, simply the modified keys will
+		//be returned by this provider to show that there is something wrong on loading it
+		ResourceBundleTranslationProvider provider = new ResourceBundleTranslationProvider(resourceBundle);
+		
 		M instance = messages.newInstance();
 		Field[] fields = messages.getDeclaredFields();
 
@@ -131,7 +177,7 @@
 			}
 
 			fields[i].set(instance,
-					provider.translate(locale, fields[i].getName()));
+					provider.translate(fields[i].getName()));
 		}
 
 		return instance;
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java
new file mode 100644
index 0000000..fb3f017
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java
@@ -0,0 +1,501 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.tools.services.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.List;
+import java.util.Locale;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.ResourceBundle.Control;
+
+import org.eclipse.e4.tools.services.ToolsServicesActivator;
+import org.eclipse.osgi.service.localization.BundleLocalization;
+import org.osgi.framework.Bundle;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Helper class for retrieving {@link ResourceBundle}s out of OSGi {@link Bundle}s.
+ * 
+ * @author Dirk Fauth
+ */
+// There is no replacement for PackageAdmin#getBundles()
+@SuppressWarnings("deprecation")
+public class ResourceBundleHelper {
+
+	/**
+	 * The schema identifier used for Eclipse platform references
+	 */
+	private static final String PLATFORM_SCHEMA = "platform"; //$NON-NLS-1$
+	/**
+	 * The schema identifier used for Eclipse bundle class references
+	 */
+	private static final String BUNDLECLASS_SCHEMA = "bundleclass"; //$NON-NLS-1$
+	/**
+	 * Identifier part of the Eclipse platform schema to point to a plugin
+	 */
+	private static final String PLUGIN_SEGMENT = "/plugin/"; //$NON-NLS-1$
+	/**
+	 * Identifier part of the Eclipse platform schema to point to a fragment
+	 */
+	private static final String FRAGMENT_SEGMENT = "/fragment/"; //$NON-NLS-1$
+	/**
+	 * The separator character for paths in the platform schema
+	 */
+	private static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$
+	
+	/**
+	 * Parses the specified contributor URI and loads the {@link ResourceBundle} for the specified {@link Locale}
+	 * out of an OSGi {@link Bundle}.
+	 * <p>Following URIs are supported:
+	 * <ul>
+	 * <li>platform:/[plugin|fragment]/[Bundle-SymbolicName]<br>
+	 * Load the OSGi resource bundle out of the bundle/fragment named [Bundle-SymbolicName]</li>
+	 * <li>platform:/[plugin|fragment]/[Bundle-SymbolicName]/[Path]/[Basename]<br>
+	 * Load the resource bundle specified by [Path] and [Basename] out of the bundle/fragment named [Bundle-SymbolicName].</li>
+	 * <li>bundleclass://[plugin|fragment]/[Full-Qualified-Classname]<br>
+	 * Instantiate the class specified by [Full-Qualified-Classname] out of the bundle/fragment named [Bundle-SymbolicName].
+	 * Note that the class needs to be a subtype of {@link ResourceBundle}.</li>
+	 * </ul>
+	 * </p>
+	 * @param contributorURI The URI that points to a {@link ResourceBundle}
+	 * @param locale The {@link Locale} to use for loading the {@link ResourceBundle}
+	 * @param localization The service for retrieving a {@link ResourceBundle} for a given {@link Locale} out of
+	 * 			the given {@link Bundle} which is specified by URI.
+	 * @return
+	 */
+	public static ResourceBundle getResourceBundleForUri(String contributorURI, Locale locale, BundleLocalization localization) {
+		if (contributorURI == null)
+			return null;
+		
+		LogService logService = ToolsServicesActivator.getDefault().getLogService();
+
+		URI uri;
+		try {
+			uri = new URI(contributorURI);
+		} catch (URISyntaxException e) {
+			if (logService != null)
+				logService.log(LogService.LOG_ERROR, "Invalid contributor URI: " + contributorURI); //$NON-NLS-1$
+			return null;
+		}
+
+		String bundleName = null;
+		Bundle bundle = null;
+		String resourcePath = null;
+		String classPath = null;
+		
+		//the uri follows the platform schema, so we search for .properties files in the bundle
+		if (PLATFORM_SCHEMA.equals(uri.getScheme())) {
+			bundleName = uri.getPath();
+			if (bundleName.startsWith(PLUGIN_SEGMENT))
+				bundleName = bundleName.substring(PLUGIN_SEGMENT.length());
+			else if (bundleName.startsWith(FRAGMENT_SEGMENT))
+				bundleName = bundleName.substring(FRAGMENT_SEGMENT.length());
+			
+			resourcePath = ""; //$NON-NLS-1$
+			if (bundleName.contains(PATH_SEPARATOR)) {
+				resourcePath = bundleName.substring(bundleName.indexOf(PATH_SEPARATOR) + 1);
+				bundleName = bundleName.substring(0, bundleName.indexOf(PATH_SEPARATOR));
+			}
+		} else if (BUNDLECLASS_SCHEMA.equals(uri.getScheme())) {
+			if (uri.getAuthority() == null) {
+				if (logService != null)
+					logService.log(LogService.LOG_ERROR, "Failed to get bundle for: " + contributorURI); //$NON-NLS-1$
+			}
+			bundleName = uri.getAuthority();
+			//remove the leading /
+			classPath = uri.getPath().substring(1);
+		}
+		
+		ResourceBundle result = null;
+
+		if (bundleName != null) {
+			bundle = getBundleForName(bundleName);
+			
+			if (bundle != null) {
+				if (resourcePath == null && classPath != null) {
+					//the URI points to a class within the bundle classpath
+					//therefore we are trying to instantiate the class
+					try {
+						Class<?> resourceBundleClass = bundle.loadClass(classPath);
+						result = getEquinoxResourceBundle(classPath, locale, resourceBundleClass.getClassLoader());
+					} catch (Exception e) {
+						if (logService != null)
+							logService.log(LogService.LOG_ERROR, "Failed to load specified ResourceBundle: " + contributorURI, e); //$NON-NLS-1$
+					}
+				}
+				else if (resourcePath.length() > 0) {
+					//the specified URI points to a resource 
+					//therefore we try to load the .properties files into a ResourceBundle
+					result = getEquinoxResourceBundle(resourcePath.replace('.', '/'), locale, bundle);
+				}
+				else {
+					//there is no class and no special resource specified within the URI 
+					//therefore we load the OSGi resource bundle out of the specified Bundle
+					//for the current Locale
+					result = localization.getLocalization(bundle, locale.toString());
+				}
+			}
+		}
+		
+		return result;
+	}
+	
+	/**
+	 * This method searches for the {@link ResourceBundle} in a modified way by inspecting the configuration option 
+	 * <code>equinox.root.locale</code>. 
+	 * <p>
+	 * If the value for this system property is set to an empty String the default search order for ResourceBundles is used:
+	 * <ul>
+	 * <li>bn + Ls + "_" + Cs + "_" + Vs</li>
+	 * <li>bn + Ls + "_" + Cs</li>
+	 * <li>bn + Ls</li>
+	 * <li>bn + Ld + "_" + Cd + "_" + Vd</li>
+	 * <li>bn + Ld + "_" + Cd</li>
+	 * <li>bn + Ld</li>
+	 * <li>bn</li>
+	 * </ul>
+	 * Where bn is this bundle's localization basename, Ls, Cs and Vs are the specified locale (language, country, variant) and 
+	 * Ld, Cd and Vd are the default locale (language, country, variant).
+	 * </p>
+	 * <p>
+	 * If Ls equals the value of <code>equinox.root.locale</code> then the following search order is used:
+	 * <ul>
+	 * <li>bn + Ls + "_" + Cs + "_" + Vs</li>
+	 * <li>bn + Ls + "_" + Cs</li>
+	 * <li>bn + Ls</li>
+	 * <li>bn</li>
+	 * <li>bn + Ld + "_" + Cd + "_" + Vd</li>
+	 * <li>bn + Ld + "_" + Cd</li>
+	 * <li>bn + Ld</li>
+	 * <li>bn</li>
+	 * </ul>
+	 * </p>
+	 * If <code>equinox.root.locale=en</code> and en_XX or en is asked for then this allows the root file to be used instead of 
+	 * falling back to the default locale.
+	 * 
+     * @param baseName the base name of the resource bundle, a fully qualified class name
+     * @param locale the locale for which a resource bundle is desired
+     * @param loader the class loader from which to load the resource bundle
+     * @return a resource bundle for the given base name and locale
+	 * 
+	 * @see ResourceBundle#getBundle(String, Locale, ClassLoader)
+	 */
+	public static ResourceBundle getEquinoxResourceBundle(String baseName, Locale locale, ClassLoader loader) {
+		ResourceBundle resourceBundle = null;
+		
+		String equinoxLocale = getEquinoxRootLocale();
+        //if the equinox.root.locale is not empty and the specified locale equals the equinox.root.locale
+		// -> use the special search order
+        if (equinoxLocale.length() > 0 && locale.toString().startsWith(equinoxLocale)) {
+        	//there is a equinox.root.locale configured that matches the specified locale 
+        	//so the special search order is used
+        	//to achieve this we first search without a fallback to the default locale
+        	resourceBundle = ResourceBundle.getBundle(baseName, locale, loader,
+                    ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT));
+        	//if there is no ResourceBundle found for that path, we will now search for the default locale ResourceBundle
+        	if (resourceBundle == null) {
+            	resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), loader,
+                        ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT));
+        	}
+        }
+        else {
+        	//there is either no equinox.root.locale configured or it does not match the specified locale
+        	// -> use the default search order
+        	resourceBundle = ResourceBundle.getBundle(baseName, locale, loader);
+        }
+
+        return resourceBundle;
+    }
+	
+	/**
+	 * This method searches for the {@link ResourceBundle} in a modified way by inspecting the configuration option 
+	 * <code>equinox.root.locale</code>. 
+	 * <p><b>Note: This method will only search for ResourceBundles based on properties files.</b></p>
+	 * <p>
+	 * If the value for this system property is set to an empty String the default search order for ResourceBundles is used:
+	 * <ul>
+	 * <li>bn + Ls + "_" + Cs + "_" + Vs</li>
+	 * <li>bn + Ls + "_" + Cs</li>
+	 * <li>bn + Ls</li>
+	 * <li>bn + Ld + "_" + Cd + "_" + Vd</li>
+	 * <li>bn + Ld + "_" + Cd</li>
+	 * <li>bn + Ld</li>
+	 * <li>bn</li>
+	 * </ul>
+	 * Where bn is this bundle's localization basename, Ls, Cs and Vs are the specified locale (language, country, variant) and 
+	 * Ld, Cd and Vd are the default locale (language, country, variant).
+	 * </p>
+	 * <p>
+	 * If Ls equals the value of <code>equinox.root.locale</code> then the following search order is used:
+	 * <ul>
+	 * <li>bn + Ls + "_" + Cs + "_" + Vs</li>
+	 * <li>bn + Ls + "_" + Cs</li>
+	 * <li>bn + Ls</li>
+	 * <li>bn</li>
+	 * <li>bn + Ld + "_" + Cd + "_" + Vd</li>
+	 * <li>bn + Ld + "_" + Cd</li>
+	 * <li>bn + Ld</li>
+	 * <li>bn</li>
+	 * </ul>
+	 * </p>
+	 * If <code>equinox.root.locale=en</code> and en_XX or en is asked for then this allows the root file to be used instead of 
+	 * falling back to the default locale.
+	 * 
+     * @param baseName the base name of the resource bundle, a fully qualified class name
+     * @param locale the locale for which a resource bundle is desired
+	 * @param bundle The OSGi {@link Bundle} to lookup the {@link ResourceBundle}
+     * @return a resource bundle for the given base name and locale
+	 * 
+	 * @see ResourceBundle#getBundle(String, Locale, Control)
+	 */
+	public static ResourceBundle getEquinoxResourceBundle(String baseName, Locale locale, Bundle bundle) {
+		ResourceBundle resourceBundle = null;
+		
+		String equinoxLocale = getEquinoxRootLocale();
+        //if the equinox.root.locale is not empty and the specified locale equals the equinox.root.locale
+		// -> use the special search order
+        if (equinoxLocale.length() > 0 && locale.toString().startsWith(equinoxLocale)) {
+        	//there is a equinox.root.locale configured that matches the specified locale 
+        	//so the special search order is used
+        	//to achieve this we first search without a fallback to the default locale
+        	resourceBundle = ResourceBundle.getBundle(baseName, locale, new BundleResourceBundleControl(bundle, false));
+        	//if there is no ResourceBundle found for that path, we will now search for the default locale ResourceBundle
+        	if (resourceBundle == null) {
+            	resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), new BundleResourceBundleControl(bundle, false));
+        	}
+        }
+        else {
+        	//there is either no equinox.root.locale configured or it does not match the specified locale
+        	// -> use the default search order
+        	resourceBundle = ResourceBundle.getBundle(baseName, locale, new BundleResourceBundleControl(bundle, true));
+        }
+		
+		return resourceBundle;
+	}
+
+	/**
+	 * @return The value for the system property for key <code>equinox.root.locale</code>.
+	 * 			If none is specified than <b>en</b> will be returned as default.
+	 */
+	private static String getEquinoxRootLocale() {
+		// Logic from FrameworkProperties.getProperty("equinox.root.locale", "en")
+		String root = System.getProperties().getProperty("equinox.root.locale"); //$NON-NLS-1$
+		if (root == null) {
+			root = "en"; //$NON-NLS-1$
+		}
+		return root;
+	}
+
+	/**
+	 * This method is copied out of org.eclipse.e4.ui.internal.workbench.Activator
+	 * because as it is a internal resource, it is not accessible for us.
+	 * 
+	 * @param bundleName
+	 *            the bundle id
+	 * @return A bundle if found, or <code>null</code>
+	 */
+	public static Bundle getBundleForName(String bundleName) {
+		PackageAdmin packageAdmin = ToolsServicesActivator.getDefault().getPackageAdmin();
+		Bundle[] bundles = packageAdmin.getBundles(bundleName, null);
+		if (bundles == null)
+			return null;
+		// Return the first bundle that is not installed or uninstalled
+		for (int i = 0; i < bundles.length; i++) {
+			if ((bundles[i].getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
+				return bundles[i];
+			}
+		}
+		return null;
+	}
+	
+    /**
+     * <p>Converts a String to a Locale.</p>
+     *
+     * <p>This method takes the string format of a locale and creates the
+     * locale object from it.</p>
+     *
+     * <pre>
+     *   MessageFactoryServiceImpl.toLocale("en")         = new Locale("en", "")
+     *   MessageFactoryServiceImpl.toLocale("en_GB")      = new Locale("en", "GB")
+     *   MessageFactoryServiceImpl.toLocale("en_GB_xxx")  = new Locale("en", "GB", "xxx")
+     * </pre>
+     *
+     * <p>This method validates the input strictly.
+     * The language code must be lowercase.
+     * The country code must be uppercase.
+     * The separator must be an underscore.
+     * The length must be correct.
+     * </p>
+     *
+     * <p>This method is inspired by <code>org.apache.commons.lang.LocaleUtils.toLocale(String)</code> by
+     * fixing the parsing error for uncommon Locales like having a language and a variant code but
+     * no country code, or a Locale that only consists of a country code.
+     * </p>
+     *  
+     * @param str the locale String to convert 
+     * @return a Locale that matches the specified locale String or <code>null</code> 
+     * 			if the specified String is <code>null</code>
+     * @throws IllegalArgumentException if the String is an invalid format
+     */	
+	public static Locale toLocale(String str) {
+        if (str == null) {
+            return null;
+        }
+        
+        String language = ""; //$NON-NLS-1$
+        String country = ""; //$NON-NLS-1$
+        String variant = ""; //$NON-NLS-1$
+        
+        String[] localeParts = str.split("_"); //$NON-NLS-1$
+        if (localeParts.length == 0 || localeParts.length > 3 
+        		|| (localeParts.length == 1 && localeParts[0].length() == 0)) {
+            throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+        } else {
+        	if (localeParts[0].length() == 1 || localeParts[0].length() > 2) {
+                throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+        	}
+        	else if (localeParts[0].length() == 2) {
+        		char ch0 = localeParts[0].charAt(0);
+        		char ch1 = localeParts[0].charAt(1);
+        		if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
+        			throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+        		}
+        	}
+            
+            language = localeParts[0];
+            
+            if (localeParts.length > 1) {
+            	if (localeParts[1].length() == 1 || localeParts[1].length() > 2) {
+                    throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+            	}
+            	else if (localeParts[1].length() == 2) {
+            		char ch3 = localeParts[1].charAt(0);
+            		char ch4 = localeParts[1].charAt(1);
+            		if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
+            			throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+            		}
+            	}
+           	
+                country = localeParts[1];
+            }
+            
+            if (localeParts.length == 3) {
+            	if (localeParts[0].length() == 0 && localeParts[1].length() == 0) {
+            		throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$
+            	}
+            	variant = localeParts[2];
+            }
+        }
+        
+        return new Locale(language, country, variant);
+    }
+	
+	/**
+	 * Specialization of {@link Control} which loads the {@link ResourceBundle} out of an
+	 * OSGi {@link Bundle} instead of using a classloader.
+	 * 
+	 * <p>It only supports properties based {@link ResourceBundle}s. If you want to use 
+	 * source based {@link ResourceBundle}s you have to use the bundleclass URI with the
+	 * Message annotation.
+	 * 
+	 * @author Dirk Fauth
+	 *
+	 */
+	static class BundleResourceBundleControl extends ResourceBundle.Control {
+		
+		/**
+		 * Flag to determine whether the default locale should be used as fallback locale
+		 * in case there is no {@link ResourceBundle} found for the specified locale.
+		 */
+		private final boolean useFallback;
+		
+		/**
+		 * The OSGi {@link Bundle} to lookup the {@link ResourceBundle}
+		 */
+		private final Bundle osgiBundle;
+		
+		/**
+		 * 
+		 * @param osgiBundle The OSGi {@link Bundle} to lookup the {@link ResourceBundle}
+		 * @param useFallback <code>true</code> if the default locale should be used as fallback
+		 * 			locale in the search path or <code>false</code> if there should be no fallback.
+		 */
+		public BundleResourceBundleControl(Bundle osgiBundle, boolean useFallback) {
+			this.osgiBundle = osgiBundle;
+			this.useFallback = useFallback;
+		}
+		
+		@Override
+		public ResourceBundle newBundle(String baseName, Locale locale,
+				String format, ClassLoader loader, boolean reload)
+				throws IllegalAccessException, InstantiationException, IOException {
+
+		    String bundleName = toBundleName(baseName, locale);
+		    ResourceBundle bundle = null;
+		    if (format.equals("java.properties")) { //$NON-NLS-1$
+				final String resourceName = toResourceName(bundleName, "properties"); //$NON-NLS-1$
+				InputStream stream = null;
+				try {
+				    stream = AccessController.doPrivileged(
+						new PrivilegedExceptionAction<InputStream>() {
+						    public InputStream run() throws IOException {
+								InputStream is = null;
+								URL url = osgiBundle.getEntry(resourceName);
+							    if (url != null) {
+									URLConnection connection = url.openConnection();
+									if (connection != null) {
+									    // Disable caches to get fresh data for
+									    // reloading.
+									    connection.setUseCaches(false);
+									    is = connection.getInputStream();
+									}
+							    }
+								return is;
+						    }
+						});
+				} catch (PrivilegedActionException e) {
+				    throw (IOException) e.getException();
+				}
+				if (stream != null) {
+				    try {
+				    	bundle = new PropertyResourceBundle(stream);
+				    } finally {
+				    	stream.close();
+				    }
+				}
+		    } 
+		    else {
+		    	throw new IllegalArgumentException("unknown format: " + format); //$NON-NLS-1$
+		    }
+		    return bundle;
+		}
+		
+		@Override
+		public List<String> getFormats(String baseName) {
+			return FORMAT_PROPERTIES;
+		}
+		
+		@Override
+		public Locale getFallbackLocale(String baseName, Locale locale) {
+			return this.useFallback ? super.getFallbackLocale(baseName, locale) : null;
+		}
+	}
+}
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java
new file mode 100644
index 0000000..163087b
--- /dev/null
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.tools.services.impl;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Wrapper class for accessing translations out of a {@link ResourceBundle}.
+ * 
+ * @author Dirk Fauth
+ *
+ */
+public class ResourceBundleTranslationProvider {
+
+	/**
+	 * The {@link ResourceBundle} to use for translations.
+	 */
+	private ResourceBundle resourceBundle;
+	
+	/**
+	 * 
+	 * @param resourceBundle The {@link ResourceBundle} to use for translations.
+	 * 			Can be <code>null</code>, which will lead to simply return the key
+	 * 			modified by prefixing and suffixing it with "!" when calling translate(String).
+	 */
+	public ResourceBundleTranslationProvider(ResourceBundle resourceBundle) {
+		this.resourceBundle = resourceBundle;
+	}
+
+	/**
+	 * Tries to retrieve the translation value for the given key out of the {@link ResourceBundle}
+	 * set to this {@link ResourceBundleTranslationProvider}. If there is no {@link ResourceBundle}
+	 * set or there is no translation found for the given key, the key itself prefixed and suffixed
+	 * with "!" will be returned to indicate that there is no translation found.
+	 * <p>This implementation also supports the usage of dot separation for property keys. As in Java
+	 * variables can not be separated with a dot, the underscore needs to be used for separation of
+	 * the variable. This will be replaced automatically to a dot, if there is no translation found
+	 * with an underscore as separator.
+	 * </p>
+	 * @param key The key of the requested translation property.
+	 * @return The translation for the given key or the key itself prefixed and suffixed
+	 * 			with "!" to indicate that there is no translation available for the
+	 * 			given key.
+	 */
+	public String translate(String key) {
+		String result = ""; //$NON-NLS-1$
+		try {
+			if (this.resourceBundle == null) {
+				result = "!" + key + "!"; //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			result = resourceBundle.getString(key);
+		} catch (MissingResourceException e) {
+			if (key.contains("_")) { //$NON-NLS-1$
+				result = translate(key.replace('_', '.'));
+			} else {
+				result = "!" + key + "!"; //$NON-NLS-1$ //$NON-NLS-2$
+			}
+		}
+		return result;
+	}
+}
diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java
index a7ee258..31c06b8 100644
--- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java
+++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java
@@ -11,20 +11,17 @@
 package org.eclipse.e4.tools.services.impl;
 
 import java.lang.reflect.ParameterizedType;
-
 import java.lang.reflect.Type;
 import java.util.Locale;
 
-import org.eclipse.e4.core.internal.contexts.ContextObjectSupplier;
-
-import org.eclipse.e4.core.internal.di.Requestor;
-import org.eclipse.e4.core.services.translation.TranslationService;
-
+import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier;
 import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
 import org.eclipse.e4.core.di.suppliers.IRequestor;
-
-import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier;
+import org.eclipse.e4.core.internal.contexts.ContextObjectSupplier;
+import org.eclipse.e4.core.internal.di.Requestor;
+import org.eclipse.e4.core.services.translation.TranslationService;
 import org.eclipse.e4.tools.services.IMessageFactoryService;
+import org.eclipse.osgi.service.localization.BundleLocalization;
 
 public class TranslationObjectSupplier extends ExtendedObjectSupplier {
 
@@ -39,10 +36,11 @@
 		
 		String locale = (String) sub.getContext().get(TranslationService.LOCALE);
 		locale = locale == null ? Locale.getDefault().toString() : locale;
+		BundleLocalization localization = sub.getContext().get(BundleLocalization.class);
 		IMessageFactoryService factoryService = sub.getContext().get(IMessageFactoryService.class); 
 		
 		try {
-			return factoryService.createInstance(locale,descriptorsClass);
+			return factoryService.createInstance(locale, descriptorsClass, localization);
 		} catch (InstantiationException e) {
 			// TODO Auto-generated catch block
 			e.printStackTrace();