Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

fix Issue #8953- "Localizing package metadata in Extension Manager" #8987

Merged
merged 14 commits into from
Sep 19, 2014

Conversation

bchintx
Copy link
Contributor

@bchintx bchintx commented Sep 5, 2014

fix Issue #8953- "Localizing package metadata in Extension Manager"

This change adds Extension Manager support for localized extension package metadata. If the localized property is present (ie. property matching the short language name), then the additional provided properties will overlay any otherwise-defined properties. These localized strings will then be displayed in the Extension Manager dialog for those extensions that provide them.

note: The localized content is completely optional.

For example, in this code sample, the French title and description properties would be overlayed with the given, localized strings.

{
    "name": "localization-example",
    "title": "Localization Example",
    "description": "A guide on how to localize your extension.",
    ...
    "i18n": [
        "en", 
        "fr" 
    ],
    "fr": {
        "title": "fr title to replace the root title",
        "description": "fr description to replace the root description"
    }
}

var lang = brackets.getLocale(),
shortLang = lang.split("-")[0];

// Extension metadata might have localized content. If so, overlay those strings.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: two spaces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcelgerber Do you mean the spaces separating the two sentences? Ok.

@marcelgerber
Copy link
Contributor

Hm, I wonder if we should have something like navigator.languages in Brackets (maybe in LocalizationUtils). It would make such use cases a lot easier.
Use cases I can think of:

  • UpdateNotification
  • package.json "i18n" parsing
  • package.json localization
  • ...

var prop;
for (prop in info.metadata[shortLang]) {
if (info.metadata[shortLang].hasOwnProperty(prop)) {
info.metadata[prop] = info.metadata[shortLang][prop];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For keywords, it's probably best to leave the original in so you can search in both languages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Will do.

@bchintx
Copy link
Contributor Author

bchintx commented Sep 6, 2014

@marcelgerber navigator.languages looks like a good idea, but probably more of a bigger user story to tackle.

Pushed code changes per review. Ready for review.

var key = useShortLang ? shortLang : lang,
prop;
for (prop in info.metadata[key]) {
if (info.metadata[key].hasOwnProperty(prop)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coding conventions recommend using _.forEach() or _.some() instead of for-in.

https://github.com/adobe/brackets/wiki/Brackets-Coding-Conventions#apis-to-use-or-avoid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@bchintx
Copy link
Contributor Author

bchintx commented Sep 6, 2014

Great suggestion, @marcelgerber.
Pushed requested code changes. Ready for review.

_.forEach(info.metadata[key], function (value, prop) {
if (prop !== "keywords") {
// overlay existing string w/ localized string
info.metadata[prop] = info.metadata[key][prop];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can now use value instead of info.metadata[key][prop]. Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch! Will do.

@marcelgerber
Copy link
Contributor

LGTM. Works great in practice.

shortLang = lang.split("-")[0],
useShortLang = info.metadata.hasOwnProperty(shortLang);
if (useShortLang || info.metadata.hasOwnProperty(lang)) {
var key = useShortLang ? shortLang : lang;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should replace the properties for both the short and the long language. Lets say that for some reason the title and description are in Spanish, so I then provide a title and description for "en", and I also provide just a title for "en-uk". If the language is "en-uk" we could first replace the title and description from "en" (as it is the shortLang) and then replace the title from "en-uk".

[shortLang, lang].forEach(function (locale) {
    if (info.metadata.hasOwnProperty(locale)) {
        _.forEach(info.metadata[locale], function (value, prop) {
            // Do the replacement ...
        });
    }
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh? I hadn't encountered that before, so thanks for the suggestion. I'll add that.

@bchintx
Copy link
Contributor Author

bchintx commented Sep 10, 2014

@peterflynn You mentioned earlier today that you had some concerns about the structure of the properties in the package.json? Can you please elaborate, and I can make the necessary changes. Thanks!

@marcelgerber
Copy link
Contributor

Hm, it would be best to let the user search for both the original and the translated title & description, but that's probably out of scope.

@@ -48,7 +48,7 @@ Move this plugin to the extensions\user\ folder to run the plugin. It will add a

* main.js – loads the Strings module for the plugin and uses mustache to localize html content

* package.json - add the translation languages as in the example: `"i18n: ["en", "fr" ]`
* package.json - add the translation languages as in the example: `"i18n: ["en", "fr" ]`. Also, add any localized metadata for displayed metadata in the Extension Manager, as in the example: `"fr": { "title": "localized title" }`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my proposed changes in #9028, this text is basically going away. It's most important to update the official package.json format docs, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Once this is merged, I can definitely update the official docs.

@peterflynn
Copy link
Member

Some higher-level feedback...

1) We probably shouldn't allow fields like name (extension id) or version or engines to vary by locale, because it could create weird and unpredictable behavior if user switches locales. Also, some of these fields are covered by package validation and it could create loopholes if the values could be overridden post-validation.

I think we should have a whitelist of which properties can be overridden. Maybe just title, description, homepage, keywords?

2) Why special-case keywords such that searches cut across locales, without doing that for other fields like name & description? It's also odd that we single out the root language for special treatment -- if an extension has 5 translations, this code only searches two: the user's locale, and the original locale the author used when writing the extension. What makes that one more special than the remaining ones?

To keep it simple, I'd suggest we not try to do any special merging or concatenating at this point, for any properties.

3) Having all the locales sit as top-level keys in package.json risks having collisions, or at least is less structured than it could be. How about we put everything underneath a single top-level key, something like this:

{
    "name": "localization-example",
    "title": "Localization Example",
    "description": "A guide on how to localize your extension.",
    ...
    "i18n": [ "en", "fr" ],
    "package-i18n": {
        "fr": {
            "title": "fr title to replace the root title",
            "description": "fr description to replace the root description"
        }
    }
}

@bchintx
Copy link
Contributor Author

bchintx commented Sep 12, 2014

@peterflynn Great observations. I can definitely do the first two.

For the third, is there a reason why you propose the additional i18n array? ie. do you foresee a need for the included languages?

@bchintx
Copy link
Contributor Author

bchintx commented Sep 12, 2014

Whoops -- ignore that last question re: the third. I see what you're referring to in your earlier inline comment.

@@ -28,7 +28,8 @@
define(function (require, exports, module) {
"use strict";

var Strings = require("strings"),
var _ = require("thirdparty/lodash"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no longer required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. Nice catch.

var lang = brackets.getLocale(),
shortLang = lang.split("-")[0];
[shortLang, lang].forEach(function (locale) {
if (info.metadata["package-i18n"] !== undefined &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this should just be if (info.metadata["package-i18n"] (without undefined check). Could also move that clause outside the forEach() if you want...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestions. Will do.

@peterflynn
Copy link
Member

@bchintx Looks good -- just two small nits.

@bchintx
Copy link
Contributor Author

bchintx commented Sep 15, 2014

I'd like to leave the property called package-i18n, rather than package-nls. With the already existing i18n property, using package-i18n IMHO helps to tie the connection between the two.

Pushing the rest of the requested code review changes.

@bchintx
Copy link
Contributor Author

bchintx commented Sep 15, 2014

Added unit tests to confirm that the translated metadata actually appears in the Extension Manager view.

@peterflynn Ready for final review.

runs(function () {
spyOn(view.model, "dispose").andCallThrough();
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates the code in describe("ExtensionManagerView") -- could you just hoist up that existing function so it can be called by both test suites?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I was trying out the Code Folding extension for the first time, and didn't notice that I had forgotten about copy-pasting from above.

@peterflynn
Copy link
Member

@bchintx Sorry, one last request to reduce code duplication in the unit tests

}
});

describe("when showing registry entries-i18n", function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we don't need another level of describe() here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Was just matching the code block above.

@peterflynn peterflynn self-assigned this Sep 17, 2014
@bchintx
Copy link
Contributor Author

bchintx commented Sep 19, 2014

@peterflynn pushed requested changes. Thanks for the review.

@peterflynn
Copy link
Member

@bchintx I had in mind to avoid duplicating toHaveText too... I've just added a commit to clean that up too -- hope you don't mind! I think this is good to merge now, and that last change is small enough that I don't think we need another round of review. Let me know if anything looks amiss in my commit post-facto!

peterflynn added a commit that referenced this pull request Sep 19, 2014
fix Issue #8953- "Localizing package metadata in Extension Manager"
@peterflynn peterflynn merged commit d1c8ce1 into master Sep 19, 2014
@peterflynn peterflynn deleted the bchin/localize-package-metadata branch September 19, 2014 08:26
@bchintx
Copy link
Contributor Author

bchintx commented Sep 19, 2014

Cool. Thanks, @peterflynn

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants