plugin.properties mechanism in eclipse RCP

13,352

Solution 1

Although I got the information wrong ... I had exactly the same problem. The plugin is not activated twice and I cannot get to the fragments Bundle-Localization key.

I want all my language translations in the plugin.properties (I know this is frowned upon but it is much easier to manage a single file).

I (half)solved the problem by using

public void populate(Bundle bundle) {
    String localisation = (String) bundle.getHeaders().get("Bundle-Localization");
    Locale locale = Locale.getDefault();

    populate(bundle.getEntry(getFileName(localisation)));
    populate(bundle.getEntry(getFileName(localisation, locale.getLanguage())));
    populate(bundle.getEntry(getFileName(localisation, locale.getLanguage(), locale.getCountry())));
    populate(bundle.getResource(getFileName("fragment")));
    populate(bundle.getResource(getFileName("fragment", locale.getLanguage())));
    populate(bundle.getResource(getFileName("fragment", locale.getLanguage(), locale.getCountry())));
}

and simply call my fragment localisation file name 'fragment.properties'.

This is not particularly elegant, but it works.

By the way, to get files from the fragment you need the getResource, it seems that fragment files are on the classpath, or are only searched when using getResource.

If someone has a better approach, please correct me.

All the best,

Mark.

Solution 2

/**
 * The Hacked NLS (National Language Support) system.
 * <p>
 * Singleton.
 * 
 * @author mima
 */
public final class HackedNLS {
    private static final HackedNLS instance = new HackedNLS();

    private final Map<String, String> translations;

    private final Set<String> knownMissing;

    /**
     * Create the NLS singleton. 
     */
    private HackedNLS() {
        translations = new HashMap<String, String>();
        knownMissing = new HashSet<String>();
    }

    /**
     * Populates the NLS key/value pairs for the current locale.
     * <p>
     * Plugin localization files may have any name as long as it is declared in the Manifest under
     * the Bundle-Localization key.
     * <p>
     * Fragments <b>MUST</b> define their localization using the base name 'fragment'.
     * This is due to the fact that I have no access to the Bundle-Localization key for the
     * fragment.
     * This may change.
     * 
     * @param bundle The bundle to use for population.
     */
    public void populate(Bundle bundle) {
        String baseName = (String) bundle.getHeaders().get("Bundle-Localization");

        populate(getLocalizedEntry(baseName, bundle));
        populate(getLocalizedEntry("fragment", bundle));
    }

    private URL getLocalizedEntry(String baseName, Bundle bundle) {
        Locale locale = Locale.getDefault();
        URL entry = bundle.getEntry(getFileName(baseName, locale.getLanguage(), locale.getCountry()));
        if (entry == null) {
            entry = bundle.getResource(getFileName(baseName, locale.getLanguage(), locale.getCountry()));
        }
        if (entry == null) {
            entry = bundle.getEntry(getFileName(baseName, locale.getLanguage()));
        }
        if (entry == null) {
            entry = bundle.getResource(getFileName(baseName, locale.getLanguage()));
        }
        if (entry == null) {
            entry = bundle.getEntry(getFileName(baseName));
        }
        if (entry == null) {
            entry = bundle.getResource(getFileName(baseName));
        }
        return entry;
    }

    private String getFileName(String baseName, String...arguments) {
        String name = baseName;
        for (int index = 0; index < arguments.length; index++) {
            name += "_" + arguments[index];
        }
        return name + ".properties";
    }

    private void populate(URL resourceUrl) {
        if (resourceUrl != null) {
            Properties props = new Properties();
            InputStream stream = null;
            try {
                stream = resourceUrl.openStream();
                props.load(stream);
            } catch (IOException e) {
                warn("Could not open the resource file " + resourceUrl, e);
            } finally {
                try {
                    stream.close();
                } catch (IOException e) {
                    warn("Could not close stream for resource file " + resourceUrl, e);
                }
            }
            for (Object key : props.keySet()) {
                translations.put((String) key, (String) props.get(key));
            }
        }
    }

    /**
     * @param key The key to translate.
     * @param arguments Array of arguments to format into the translated text. May be empty.
     * @return The formatted translated string.
     */
    public String getTranslated(String key, Object...arguments) {
        String translation = translations.get(key);
        if (translation != null) {
            if (arguments != null) {
                translation = MessageFormat.format(translation, arguments);
            }
        } else {
            translation = "!! " + key;
            if (!knownMissing.contains(key)) {
                warn("Could not find any translation text for " + key, null);
                knownMissing.add(key);
            }
        }
        return translation;
    }

    private void warn(String string, Throwable cause) {
        Status status;
        if (cause == null) {
            status = new Status(
                    IStatus.ERROR, 
                    MiddlewareActivator.PLUGIN_ID, 
                    string);
        } else {
            status = new Status(
                IStatus.ERROR, 
                MiddlewareActivator.PLUGIN_ID, 
                string,
                cause);
        }
        MiddlewareActivator.getDefault().getLog().log(status);

    }

    /**
     * @return The NLS instance.
     */
    public static HackedNLS getInstance() {
        return instance;
    }

    /**
     * @param key The key to translate.
     * @param arguments Array of arguments to format into the translated text. May be empty.
     * @return The formatted translated string.
     */
    public static String getText(String key, Object...arguments) {
        return getInstance().getTranslated(key, arguments);
    }
}
Share:
13,352
Markus Lausberg
Author by

Markus Lausberg

Software Developer, Automotive Applications. Software/Tools i am using: Eclipse Java C/C++ JavaScript SVN ANT Microsoft Project Sparx Systems - Enterprise Architect Polarion Profiler (JProfiler, JVisualVM, JMC) JetBrains - WebStorm Oxygen XML Editor

Updated on June 06, 2022

Comments

  • Markus Lausberg
    Markus Lausberg about 2 years

    My project includes multiple plugins and every plugin includes the plugin.properties file with near to 20 translations. The MANIFEST.MF file defines the name of the properties files where the external plugin strings are stored.

    Bundle-Localization: plugin
    

    The name of the plugin i define like

    %plugin.name
    

    Eclipse will search the "%plugin.name" in the plugin.properties file at runtime.

    Which class read out the MANIFEST.MF Bundle-Localization entry and at which point is the string with the starting '%' suffix is searched in the "plugin.properties" file?

    I want to find and patch these class in that way, that i can first look into some other directories/files for the "%plugin.name" identifier. With these new mechanism i can add fragments to my product and overwrite single lines in a "plugin.properties" file without changing the original plugin. With these mechanism i could create a build process for multiple customers just by adding different fragments. The fragments including the customer names and special string they want to change.

    I want to do it that way, because the fragment mechanism only add files to the original plugin. When the "plugin.properties" file is existing in the plugin, the fragment "plugin.properties" files are ignored.

    UPDATE 1:

    The method

    class ManifestLocalization{
    ...
    protected ResourceBundle getResourceBundle(String localeString) {
    }
    ...
    }
    

    returns the ResourceBundle of the properties file for the given locale string. When somebody nows how i can now first look into the fragment to get the resource path please post it.

    UPDATE 2:

    The method in class ManifestLocalization

        private URL findInResolved(String filePath, AbstractBundle bundleHost) {
    
            URL result = findInBundle(filePath, bundleHost);
            if (result != null)
                return result;
            return findInFragments(filePath, bundleHost);
        }
    

    Searchs for the properties file and cache it. The translations can than get from the cached file. The problem is, that the complete file is cached and not single translations.

    A solution would be to first read the fragment file, than read the bundle file. When both files are existing merge them into one file and write the new properties file to the disk. The URL of the new properties file returns, so that the new propetries file can cached.

  • Markus Lausberg
    Markus Lausberg about 15 years
    Where i can find the populate method? Or do i have to write it?
  • Markus Lausberg
    Markus Lausberg about 15 years
    Can you please put more informations into your answer. Thank you!
  • Mark Miller
    Mark Miller about 15 years
    The getLocalizedEntry bundle.getResource and bundle.getEntry are required as files on the classpath (those found in the fragment) need to the getResource whereas those in the plugin root (the plugin localization) require getEntry. Again, if anyone has a better answer please let us know as I would also be interested.
  • Peteter
    Peteter over 14 years
    I'm unclear how to use the class you have provided. Could it be used to translate for instance a perspective name declared in a plugin.xml file, in a different way than looking in the bundle.properties file?
  • Peteter
    Peteter over 14 years
    Do you mean the suggested way from Mark Miller or what Mark? :)