DEV: Upgrade the MessageFormat library (JS)
This patch upgrades the MessageFormat library to version 3.3.0 from 0.1.5. Our `I18n.messageFormat` method signature is unchanged, and now uses the new API under the hood. We don’t need dedicated locale files for handling pluralization rules anymore as everything is now included by the library itself. The compilation of the messages now happens through our `messageformat-wrapper` gem. It then outputs an ES module that includes all its needed dependencies. Most of the changes happen in `JsLocaleHelper` and in the `ExtraLocales` controller. A new method called `.output_MF` has been introduced in `JsLocaleHelper`. It handles all the fetching, compiling and transpiling to generate the proper MF messages in JS. Overrides and fallbacks are also handled directly in this method. The other main change is that now the MF translations are served through the `ExtraLocales` controller instead of being statically compiled in a JS file, then having to patch the messages using overrides and fallbacks. Now the MF translations are just another bundle that is created on the fly and cached by the client.
This commit is contained in:
parent
6591a0654b
commit
301713ef96
1
Gemfile
1
Gemfile
|
@ -89,6 +89,7 @@ gem "mini_sql"
|
|||
gem "pry-rails", require: false
|
||||
gem "pry-byebug", require: false
|
||||
gem "rtlcss", require: false
|
||||
gem "messageformat-wrapper", require: false
|
||||
gem "rake"
|
||||
|
||||
gem "thor", require: false
|
||||
|
|
|
@ -234,6 +234,8 @@ GEM
|
|||
memory_profiler (1.0.2)
|
||||
message_bus (4.3.8)
|
||||
rack (>= 1.1.3)
|
||||
messageformat-wrapper (1.0.0)
|
||||
mini_racer (>= 0.6.3)
|
||||
method_source (1.1.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_racer (0.9.0)
|
||||
|
@ -646,6 +648,7 @@ DEPENDENCIES
|
|||
maxminddb
|
||||
memory_profiler
|
||||
message_bus
|
||||
messageformat-wrapper
|
||||
mini_mime
|
||||
mini_racer
|
||||
mini_scheduler
|
||||
|
@ -728,4 +731,4 @@ DEPENDENCIES
|
|||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.3
|
||||
2.5.9
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@embroider/addon-shim": "^1.8.9"
|
||||
"@embroider/addon-shim": "^1.8.9",
|
||||
"make-plural": "^7.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
|
|
|
@ -4,6 +4,8 @@ if (window.I18n) {
|
|||
);
|
||||
}
|
||||
|
||||
import * as Cardinals from "make-plural/cardinals";
|
||||
|
||||
// The placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
|
||||
const PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
|
||||
const SEPARATOR = ".";
|
||||
|
@ -13,19 +15,14 @@ export class I18n {
|
|||
defaultLocale = "en";
|
||||
|
||||
// Set current locale to null
|
||||
local = null;
|
||||
locale = null;
|
||||
fallbackLocale = null;
|
||||
translations = null;
|
||||
extras = null;
|
||||
noFallbacks = false;
|
||||
testing = false;
|
||||
|
||||
// Set default pluralization rule
|
||||
pluralizationRules = {
|
||||
en(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||
},
|
||||
};
|
||||
pluralizationRules = Cardinals;
|
||||
|
||||
translate = (scope, options) => this._translate(scope, options);
|
||||
|
||||
|
@ -36,6 +33,13 @@ export class I18n {
|
|||
return this.locale || this.defaultLocale;
|
||||
}
|
||||
|
||||
get pluralizationNormalizedLocale() {
|
||||
if (this.currentLocale() === "pt") {
|
||||
return "pt_PT";
|
||||
}
|
||||
return this.currentLocale().replace(/[_-].*/, "");
|
||||
}
|
||||
|
||||
enableVerboseLocalization() {
|
||||
let counter = 0;
|
||||
let keys = {};
|
||||
|
@ -192,7 +196,9 @@ export class I18n {
|
|||
options = this.prepareOptions(options);
|
||||
let count = options.count.toString();
|
||||
|
||||
let pluralizer = this.pluralizer(options.locale || this.currentLocale());
|
||||
let pluralizer = this.pluralizer(
|
||||
options.locale || this.pluralizationNormalizedLocale
|
||||
);
|
||||
let key = pluralizer(Math.abs(count));
|
||||
let keys = typeof key === "object" && key instanceof Array ? key : [key];
|
||||
let message = this.findAndTranslateValidNode(keys, translation);
|
||||
|
@ -371,6 +377,22 @@ export class I18n {
|
|||
isValidNode(obj, node) {
|
||||
return obj[node] !== null && obj[node] !== undefined;
|
||||
}
|
||||
|
||||
messageFormat(key, options) {
|
||||
const message = this._mfMessages.hasMessage(
|
||||
key,
|
||||
this._mfMessages.locale,
|
||||
this._mfMessages.defaultLocale
|
||||
);
|
||||
if (!message) {
|
||||
return "Missing Key: " + key;
|
||||
}
|
||||
try {
|
||||
return this._mfMessages.get(key, options);
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class I18nMissingInterpolationArgument extends Error {
|
||||
|
|
|
@ -39,10 +39,5 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(I18n._mfOverrides || {})) {
|
||||
key = key.replace(/^[a-z_]*js\./, "");
|
||||
I18n._compiledMFs[key] = value;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -64,3 +64,12 @@ loaderShim("truth-helpers/helpers/not", () =>
|
|||
loaderShim("truth-helpers/helpers/or", () =>
|
||||
importSync("truth-helpers/helpers/or")
|
||||
);
|
||||
loaderShim("@messageformat/runtime/messages", () =>
|
||||
importSync("@messageformat/runtime/messages")
|
||||
);
|
||||
loaderShim("@messageformat/runtime", () =>
|
||||
importSync("@messageformat/runtime")
|
||||
);
|
||||
loaderShim("@messageformat/runtime/lib/cardinals", () =>
|
||||
importSync("@messageformat/runtime/lib/cardinals")
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ const Yaml = require("js-yaml");
|
|||
const fs = require("fs");
|
||||
const concat = require("broccoli-concat");
|
||||
const mergeTrees = require("broccoli-merge-trees");
|
||||
const MessageFormat = require("messageformat");
|
||||
const MessageFormat = require("@messageformat/core");
|
||||
const deepmerge = require("deepmerge");
|
||||
const glob = require("glob");
|
||||
const { shouldLoadPlugins } = require("discourse-plugins");
|
||||
|
@ -34,7 +34,7 @@ class TranslationPlugin extends Plugin {
|
|||
} else if (key.endsWith("_MF")) {
|
||||
// omit locale.js
|
||||
let mfPath = subpath.slice(2).join(".");
|
||||
formats[mfPath] = this.mf.precompile(this.mf.parse(value));
|
||||
formats[mfPath] = this.mf.compile(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -74,11 +74,19 @@ class TranslationPlugin extends Plugin {
|
|||
formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`);
|
||||
|
||||
let contents = `
|
||||
I18n.locale = 'en';
|
||||
I18n.translations = ${JSON.stringify(parsed)};
|
||||
I18n.extras = ${JSON.stringify(extras)};
|
||||
MessageFormat = { locale: {} };
|
||||
I18n._compiledMFs = { ${formats.join(",\n")} };
|
||||
(function() {
|
||||
I18n.locale = 'en';
|
||||
I18n.translations = ${JSON.stringify(parsed)};
|
||||
I18n.extras = ${JSON.stringify(extras)};
|
||||
|
||||
const Messages = require("@messageformat/runtime/messages").default;
|
||||
const { number, plural, select } = require("@messageformat/runtime");
|
||||
const { en } = require("@messageformat/runtime/lib/cardinals");
|
||||
const msgData = { en: { ${formats.join(",\n")} } };
|
||||
const messages = new Messages(msgData, "en");
|
||||
messages.defaultLocale = "en";
|
||||
I18n._mfMessages = messages;
|
||||
})()
|
||||
`;
|
||||
|
||||
fs.writeFileSync(
|
||||
|
@ -110,7 +118,6 @@ module.exports.createI18nTree = function (discourseRoot, vendorJs) {
|
|||
mergeTrees([
|
||||
vendorJs,
|
||||
discourseRoot + "/app/assets/javascripts/locales",
|
||||
discourseRoot + "/lib/javascripts",
|
||||
en,
|
||||
]),
|
||||
{
|
||||
|
@ -118,17 +125,10 @@ module.exports.createI18nTree = function (discourseRoot, vendorJs) {
|
|||
"i18n.js",
|
||||
"moment.js",
|
||||
"moment-timezone-with-data.js",
|
||||
"messageformat-lookup.js",
|
||||
"locale/en.js",
|
||||
"client.en.js",
|
||||
],
|
||||
headerFiles: [
|
||||
"i18n.js",
|
||||
"moment.js",
|
||||
"moment-timezone-with-data.js",
|
||||
"messageformat-lookup.js",
|
||||
],
|
||||
footerFiles: ["client.en.js", "locale/en.js"],
|
||||
headerFiles: ["i18n.js", "moment.js", "moment-timezone-with-data.js"],
|
||||
footerFiles: ["client.en.js"],
|
||||
outputFile: `assets/test-i18n.js`,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
"@faker-js/faker": "^8.4.1",
|
||||
"@glimmer/syntax": "^0.92.0",
|
||||
"@highlightjs/cdn-assets": "^11.10.0",
|
||||
"@messageformat/runtime": "^3.0.1",
|
||||
"ace-builds": "^1.35.2",
|
||||
"decorator-transforms": "^2.0.0",
|
||||
"discourse-hbr": "1.0.0",
|
||||
"discourse-widget-hbs": "1.0.0",
|
||||
"ember-route-template": "^1.0.3",
|
||||
|
@ -29,8 +31,7 @@
|
|||
"highlight.js": "^11.10.0",
|
||||
"jspreadsheet-ce": "^4.13.4",
|
||||
"morphlex": "^0.0.16",
|
||||
"pretty-text": "1.0.0",
|
||||
"decorator-transforms": "^2.0.0"
|
||||
"pretty-text": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
|
@ -105,7 +106,6 @@
|
|||
"js-yaml": "^4.1.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"message-bus-client": "^4.3.8",
|
||||
"messageformat": "0.1.5",
|
||||
"pretender": "^3.4.7",
|
||||
"qunit": "^2.21.0",
|
||||
"qunit-dom": "^3.2.0",
|
||||
|
@ -129,4 +129,4 @@
|
|||
"ember": {
|
||||
"edition": "octane"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,13 @@ module("Unit | Utility | i18n", function (hooks) {
|
|||
with_multiple_interpolate_arguments: "Hi %{username}, %{username2}",
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
js: {
|
||||
topic_stat_sentence_week: {
|
||||
other: "先週、新しいトピックが %{count} 件投稿されました。",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// fake pluralization rules
|
||||
|
@ -172,18 +179,6 @@ module("Unit | Utility | i18n", function (hooks) {
|
|||
},
|
||||
},
|
||||
};
|
||||
I18n.pluralizationRules.pl_PL = function (n) {
|
||||
if (n === 1) {
|
||||
return "one";
|
||||
}
|
||||
if (n % 10 >= 2 && n % 10 <= 4) {
|
||||
return "few";
|
||||
}
|
||||
if (n % 10 === 0) {
|
||||
return "many";
|
||||
}
|
||||
return "other";
|
||||
};
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.t("admin.dashboard.title"),
|
||||
|
@ -218,6 +213,20 @@ module("Unit | Utility | i18n", function (hooks) {
|
|||
assert.strictEqual(I18n.t("word_count", { count: 3 }), "3 words");
|
||||
assert.strictEqual(I18n.t("word_count", { count: 10 }), "10 words");
|
||||
assert.strictEqual(I18n.t("word_count", { count: 100 }), "100 words");
|
||||
|
||||
I18n.locale = "ja";
|
||||
assert.strictEqual(
|
||||
I18n.t("topic_stat_sentence_week", { count: 0 }),
|
||||
"先週、新しいトピックが 0 件投稿されました。"
|
||||
);
|
||||
assert.strictEqual(
|
||||
I18n.t("topic_stat_sentence_week", { count: 1 }),
|
||||
"先週、新しいトピックが 1 件投稿されました。"
|
||||
);
|
||||
assert.strictEqual(
|
||||
I18n.t("topic_stat_sentence_week", { count: 2 }),
|
||||
"先週、新しいトピックが 2 件投稿されました。"
|
||||
);
|
||||
});
|
||||
|
||||
test("adds the count to the missing translation strings", function (assert) {
|
||||
|
@ -323,4 +332,28 @@ module("Unit | Utility | i18n", function (hooks) {
|
|||
I18n.testing = false;
|
||||
}
|
||||
});
|
||||
|
||||
test("pluralizationNormalizedLocale", function (assert) {
|
||||
I18n.locale = "pt";
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.pluralizationNormalizedLocale,
|
||||
"pt_PT",
|
||||
"returns 'pt_PT' for the 'pt' locale, this is a special case of the 'make-plural' lib."
|
||||
);
|
||||
|
||||
Object.entries({
|
||||
pt_BR: "pt",
|
||||
en_GB: "en",
|
||||
bs_BA: "bs",
|
||||
"fr-BE": "fr",
|
||||
}).forEach(([raw, normalized]) => {
|
||||
I18n.locale = raw;
|
||||
assert.strictEqual(
|
||||
I18n.pluralizationNormalizedLocale,
|
||||
normalized,
|
||||
`returns '${normalized}' for '${raw}'`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,9 +10,7 @@ module("initializer:localization", function (hooks) {
|
|||
this._locale = I18n.locale;
|
||||
this._translations = I18n.translations;
|
||||
this._extras = I18n.extras;
|
||||
this._compiledMFs = I18n._compiledMFs;
|
||||
this._overrides = I18n._overrides;
|
||||
this._mfOverrides = I18n._mfOverrides;
|
||||
|
||||
I18n.locale = "fr";
|
||||
|
||||
|
@ -37,10 +35,6 @@ module("initializer:localization", function (hooks) {
|
|||
},
|
||||
};
|
||||
|
||||
I18n._compiledMFs = {
|
||||
"user.messages.some_key_MF": () => "user.messages.some_key_MF (FR)",
|
||||
};
|
||||
|
||||
I18n.extras = {
|
||||
fr: {
|
||||
admin: {
|
||||
|
@ -67,9 +61,7 @@ module("initializer:localization", function (hooks) {
|
|||
I18n.locale = this._locale;
|
||||
I18n.translations = this._translations;
|
||||
I18n.extras = this._extras;
|
||||
I18n._compiledMFs = this._compiledMFs;
|
||||
I18n._overrides = this._overrides;
|
||||
I18n._mfOverrides = this._mfOverrides;
|
||||
});
|
||||
|
||||
test("translation overrides", function (assert) {
|
||||
|
@ -159,21 +151,6 @@ module("initializer:localization", function (hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
test("translation overrides for MessageFormat strings", function (assert) {
|
||||
I18n._mfOverrides = {
|
||||
"js.user.messages.some_key_MF": () =>
|
||||
"user.messages.some_key_MF (FR override)",
|
||||
};
|
||||
|
||||
LocalizationInitializer.initialize(this.owner);
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.messageFormat("user.messages.some_key_MF", {}),
|
||||
"user.messages.some_key_MF (FR override)",
|
||||
"overrides existing MessageFormat string"
|
||||
);
|
||||
});
|
||||
|
||||
test("skip translation override if parent node is not an object", function (assert) {
|
||||
I18n._overrides = {
|
||||
fr: {
|
||||
|
|
|
@ -11,6 +11,61 @@ class ExtraLocalesController < ApplicationController
|
|||
|
||||
OVERRIDES_BUNDLE ||= "overrides"
|
||||
MD5_HASH_LENGTH ||= 32
|
||||
MF_BUNDLE = "mf"
|
||||
BUNDLES = [OVERRIDES_BUNDLE, MF_BUNDLE]
|
||||
|
||||
class << self
|
||||
def js_digests
|
||||
@js_digests ||= {}
|
||||
end
|
||||
|
||||
def bundle_js_hash(bundle)
|
||||
bundle_key = "#{bundle}_#{I18n.locale}"
|
||||
if bundle.in?(BUNDLES)
|
||||
site = RailsMultisite::ConnectionManagement.current_db
|
||||
|
||||
js_digests[site] ||= {}
|
||||
js_digests[site][bundle_key] ||= begin
|
||||
js = bundle_js(bundle)
|
||||
js.present? ? Digest::MD5.hexdigest(js) : nil
|
||||
end
|
||||
else
|
||||
js_digests[bundle_key] ||= Digest::MD5.hexdigest(bundle_js(bundle))
|
||||
end
|
||||
end
|
||||
|
||||
def url(bundle)
|
||||
"#{Discourse.base_path}/extra-locales/#{bundle}?v=#{bundle_js_hash(bundle)}"
|
||||
end
|
||||
|
||||
def client_overrides_exist?
|
||||
bundle_js_hash(OVERRIDES_BUNDLE).present?
|
||||
end
|
||||
|
||||
def bundle_js(bundle)
|
||||
locale_str = I18n.locale.to_s
|
||||
bundle_str = "#{bundle}_js"
|
||||
|
||||
case bundle
|
||||
when OVERRIDES_BUNDLE
|
||||
JsLocaleHelper.output_client_overrides(locale_str)
|
||||
when MF_BUNDLE
|
||||
JsLocaleHelper.output_MF(locale_str)
|
||||
else
|
||||
JsLocaleHelper.output_extra_locales(bundle_str, locale_str)
|
||||
end
|
||||
end
|
||||
|
||||
def bundle_js_with_hash(bundle)
|
||||
js = bundle_js(bundle)
|
||||
[js, Digest::MD5.hexdigest(js)]
|
||||
end
|
||||
|
||||
def clear_cache!
|
||||
site = RailsMultisite::ConnectionManagement.current_db
|
||||
js_digests.delete(site)
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
bundle = params[:bundle]
|
||||
|
@ -18,60 +73,18 @@ class ExtraLocalesController < ApplicationController
|
|||
|
||||
version = params[:v]
|
||||
if version.present?
|
||||
if version.kind_of?(String) && version.length == MD5_HASH_LENGTH
|
||||
hash = ExtraLocalesController.bundle_js_hash(bundle)
|
||||
immutable_for(1.year) if hash == version
|
||||
else
|
||||
raise Discourse::InvalidParameters.new(:v)
|
||||
end
|
||||
raise Discourse::InvalidParameters.new(:v) unless version.to_s.size == MD5_HASH_LENGTH
|
||||
end
|
||||
|
||||
render plain: ExtraLocalesController.bundle_js(bundle), content_type: "application/javascript"
|
||||
end
|
||||
content, hash = ExtraLocalesController.bundle_js_with_hash(bundle)
|
||||
immutable_for(1.year) if hash == version
|
||||
|
||||
def self.bundle_js_hash(bundle)
|
||||
if bundle == OVERRIDES_BUNDLE
|
||||
site = RailsMultisite::ConnectionManagement.current_db
|
||||
|
||||
@by_site ||= {}
|
||||
@by_site[site] ||= {}
|
||||
@by_site[site][I18n.locale] ||= begin
|
||||
js = bundle_js(bundle)
|
||||
js.present? ? Digest::MD5.hexdigest(js) : nil
|
||||
end
|
||||
else
|
||||
@bundle_js_hash ||= {}
|
||||
@bundle_js_hash["#{bundle}_#{I18n.locale}"] ||= Digest::MD5.hexdigest(bundle_js(bundle))
|
||||
end
|
||||
end
|
||||
|
||||
def self.url(bundle)
|
||||
"#{Discourse.base_path}/extra-locales/#{bundle}?v=#{bundle_js_hash(bundle)}"
|
||||
end
|
||||
|
||||
def self.client_overrides_exist?
|
||||
bundle_js_hash(OVERRIDES_BUNDLE).present?
|
||||
end
|
||||
|
||||
def self.bundle_js(bundle)
|
||||
locale_str = I18n.locale.to_s
|
||||
bundle_str = "#{bundle}_js"
|
||||
|
||||
if bundle == OVERRIDES_BUNDLE
|
||||
JsLocaleHelper.output_client_overrides(locale_str)
|
||||
else
|
||||
JsLocaleHelper.output_extra_locales(bundle_str, locale_str)
|
||||
end
|
||||
end
|
||||
|
||||
def self.clear_cache!
|
||||
site = RailsMultisite::ConnectionManagement.current_db
|
||||
@by_site&.delete(site)
|
||||
render plain: content, content_type: "application/javascript"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_bundle?(bundle)
|
||||
bundle == OVERRIDES_BUNDLE || (bundle =~ /\A(admin|wizard)\z/ && current_user&.staff?)
|
||||
bundle.in?(BUNDLES) || (bundle =~ /\A(admin|wizard)\z/ && current_user&.staff?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,6 +48,14 @@ class TranslationOverride < ActiveRecord::Base
|
|||
attribute :status, :integer
|
||||
enum status: { up_to_date: 0, outdated: 1, invalid_interpolation_keys: 2, deprecated: 3 }
|
||||
|
||||
scope :mf_locales, ->(locale) { where(locale: locale).where("translation_key LIKE '%_MF'") }
|
||||
scope :client_locales,
|
||||
->(locale) do
|
||||
where(locale: locale)
|
||||
.where("translation_key LIKE 'js.%' OR translation_key LIKE 'admin_js.%'")
|
||||
.where.not("translation_key LIKE '%_MF'")
|
||||
end
|
||||
|
||||
def self.upsert!(locale, key, value)
|
||||
params = { locale: locale, translation_key: key }
|
||||
|
||||
|
@ -58,10 +66,6 @@ class TranslationOverride < ActiveRecord::Base
|
|||
I18n.overrides_disabled { I18n.t(transform_pluralized_key(key), locale: :en) }
|
||||
|
||||
data = { value: sanitized_value, original_translation: original_translation }
|
||||
if key.end_with?("_MF")
|
||||
_, filename = JsLocaleHelper.find_message_format_locale([locale], fallback_to_english: false)
|
||||
data[:compiled_js] = JsLocaleHelper.compile_message_format(filename, locale, sanitized_value)
|
||||
end
|
||||
|
||||
params.merge!(data) if translation_override.new_record?
|
||||
i18n_changed(locale, [key]) if translation_override.update(data)
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<% add_resource_preload_list(script_asset_path("browser-update"), "script") %>
|
||||
<link rel="preload" href="<%= script_asset_path "start-discourse" %>" as="script" nonce="<%= csp_nonce_placeholder %>">
|
||||
<link rel="preload" href="<%= script_asset_path "browser-update" %>" as="script" nonce="<%= csp_nonce_placeholder %>">
|
||||
|
||||
|
||||
<%= preload_script 'browser-detect' %>
|
||||
|
||||
<%= preload_script "vendor" %>
|
||||
|
@ -37,8 +37,9 @@
|
|||
<%- end %>
|
||||
|
||||
<%= preload_script "locales/#{I18n.locale}" %>
|
||||
<%= preload_script_url ExtraLocalesController.url("mf") %>
|
||||
<%- if ExtraLocalesController.client_overrides_exist? %>
|
||||
<%= preload_script_url ExtraLocalesController.url('overrides') %>
|
||||
<%= preload_script_url ExtraLocalesController.url("overrides") %>
|
||||
<%- end %>
|
||||
|
||||
<%- if staff? %>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<%= preload_script "discourse" %>
|
||||
<%= preload_script "test" %>
|
||||
<%= preload_script "locales/#{I18n.locale}" %>
|
||||
<%= preload_script_url ExtraLocalesController.url("mf") %>
|
||||
<%= preload_script "admin" %>
|
||||
<%- Discourse.find_plugin_js_assets(include_disabled: true).each do |file| %>
|
||||
<%= preload_script file %>
|
||||
|
@ -50,7 +51,7 @@
|
|||
<% elsif @suggested_themes %>
|
||||
<h2>Theme QUnit Test Runner</h2>
|
||||
|
||||
<%- if @suggested_themes.size == 0 %>
|
||||
<%- if @suggested_themes.empty? %>
|
||||
<p>Cannot find any theme tests.</p>
|
||||
<%- else %>
|
||||
<h3>Select a theme/component: </h3>
|
||||
|
|
|
@ -916,7 +916,6 @@ module Discourse
|
|||
PrettyText.reset_context
|
||||
|
||||
DiscourseJsProcessor::Transpiler.reset_context if defined?(DiscourseJsProcessor::Transpiler)
|
||||
JsLocaleHelper.reset_context if defined?(JsLocaleHelper)
|
||||
|
||||
# warm up v8 after fork, that way we do not fork a v8 context
|
||||
# it may cause issues if bg threads in a v8 isolate randomly stop
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.af = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.am = function(n) {
|
||||
if (n === 0 || n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
MessageFormat.locale.ar = function(n) {
|
||||
if (n === 0) {
|
||||
return 'zero';
|
||||
}
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2) {
|
||||
return 'two';
|
||||
}
|
||||
if ((n % 100) >= 3 && (n % 100) <= 10 && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
if ((n % 100) >= 11 && (n % 100) <= 99 && n == Math.floor(n)) {
|
||||
return 'many';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
MessageFormat.locale.be = function (n) {
|
||||
var r10 = n % 10, r100 = n % 100;
|
||||
|
||||
if (r10 == 1 && r100 != 11)
|
||||
return 'one';
|
||||
|
||||
if (r10 >= 2 && r10 <= 4 && (r100 < 12 || r100 > 14) && n == Math.floor(n))
|
||||
return 'few';
|
||||
|
||||
if ((r10 == 0 || (r10 >= 5 && r10 <= 9) || (r100 >= 11 && r100 <= 14)) && n == Math.floor(n))
|
||||
return 'many';
|
||||
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.bg = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.bn = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
MessageFormat.locale.br = function (n) {
|
||||
if (n === 0) {
|
||||
return 'zero';
|
||||
}
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2) {
|
||||
return 'two';
|
||||
}
|
||||
if (n == 3) {
|
||||
return 'few';
|
||||
}
|
||||
if (n == 6) {
|
||||
return 'many';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
MessageFormat.locale.bs = function (n) {
|
||||
if ((n % 10) == 1 && (n % 100) != 11) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 10) >= 2 && (n % 10) <= 4 &&
|
||||
((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ca = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.cs = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2 || n == 3 || n == 4) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
MessageFormat.locale.cy = function (n) {
|
||||
if (n === 0) {
|
||||
return 'zero';
|
||||
}
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2) {
|
||||
return 'two';
|
||||
}
|
||||
if (n == 3) {
|
||||
return 'few';
|
||||
}
|
||||
if (n == 6) {
|
||||
return 'many';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.da = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.de = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.el = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.en = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.es = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.et = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.eu = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.fa = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.fa_IR = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.fi = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.fil = function(n) {
|
||||
if (n === 0 || n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.fr = function (n) {
|
||||
if (n >= 0 && n < 2) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.ga = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2) {
|
||||
return 'two';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.gl = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.gsw = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.gu = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.he = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.hi = function(n) {
|
||||
if (n === 0 || n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
MessageFormat.locale.hr = function (n) {
|
||||
if ((n % 10) == 1 && (n % 100) != 11) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 10) >= 2 && (n % 10) <= 4 &&
|
||||
((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.hu = function(n) {
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.hy = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.id = function(n) {
|
||||
return 'other';
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale["in"] = function(n) {
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.is = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.it = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.iw = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.ja = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.kn = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.ko = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.lag = function (n) {
|
||||
if (n === 0) {
|
||||
return 'zero';
|
||||
}
|
||||
if (n > 0 && n < 2) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ln = function(n) {
|
||||
if (n === 0 || n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
MessageFormat.locale.lt = function (n) {
|
||||
if ((n % 10) == 1 && ((n % 100) < 11 || (n % 100) > 19)) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 10) >= 2 && (n % 10) <= 9 &&
|
||||
((n % 100) < 11 || (n % 100) > 19) && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.lv = function (n) {
|
||||
if (n === 0) {
|
||||
return 'zero';
|
||||
}
|
||||
if ((n % 10) == 1 && (n % 100) != 11) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.mk = function (n) {
|
||||
if ((n % 10) == 1 && n != 11) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ml = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
MessageFormat.locale.mo = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n === 0 || n != 1 && (n % 100) >= 1 &&
|
||||
(n % 100) <= 19 && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.mr = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.ms = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
MessageFormat.locale.mt = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n === 0 || ((n % 100) >= 2 && (n % 100) <= 4 && n == Math.floor(n))) {
|
||||
return 'few';
|
||||
}
|
||||
if ((n % 100) >= 11 && (n % 100) <= 19 && n == Math.floor(n)) {
|
||||
return 'many';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.nl = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.no = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.or = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
MessageFormat.locale.pl_PL = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 10) >= 2 && (n % 10) <= 4 &&
|
||||
((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
if ((n % 10) === 0 || n != 1 && (n % 10) == 1 ||
|
||||
((n % 10) >= 5 && (n % 10) <= 9 || (n % 100) >= 12 && (n % 100) <= 14) &&
|
||||
n == Math.floor(n)) {
|
||||
return 'many';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.pt = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.pt_BR = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
MessageFormat.locale.ro = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n === 0 || n != 1 && (n % 100) >= 1 &&
|
||||
(n % 100) <= 19 && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
MessageFormat.locale.ru = function (n) {
|
||||
var r10 = n % 10, r100 = n % 100;
|
||||
|
||||
if (r10 == 1 && r100 != 11)
|
||||
return 'one';
|
||||
|
||||
if (r10 >= 2 && r10 <= 4 && (r100 < 12 || r100 > 14) && n == Math.floor(n))
|
||||
return 'few';
|
||||
|
||||
if (r10 === 0 || (r10 >= 5 && r10 <= 9) ||
|
||||
(r100 >= 11 && r100 <= 14) && n == Math.floor(n)) {
|
||||
return 'many';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.shi = function(n) {
|
||||
if (n >= 0 && n <= 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n >= 2 && n <= 10 && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
MessageFormat.locale.sk = function (n) {
|
||||
if (n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if (n == 2 || n == 3 || n == 4) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
MessageFormat.locale.sl = function (n) {
|
||||
if ((n % 100) == 1) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 100) == 2) {
|
||||
return 'two';
|
||||
}
|
||||
if ((n % 100) == 3 || (n % 100) == 4) {
|
||||
return 'few';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.sq = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
MessageFormat.locale.sr = function (n) {
|
||||
var r10 = n % 10, r100 = n % 100;
|
||||
|
||||
if (r10 == 1 && r100 != 11)
|
||||
return 'one';
|
||||
|
||||
if (r10 >= 2 && r10 <= 4 && (r100 < 12 || r100 > 14) && n == Math.floor(n))
|
||||
return 'few';
|
||||
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.sv = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.sw = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ta = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.te = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.th = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.tl = function(n) {
|
||||
if (n === 0 || n == 1) {
|
||||
return 'one';
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.tr = function(n) {
|
||||
return 'other';
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.tr_TR = function(n) {
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ug = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
MessageFormat.locale.uk = function (n) {
|
||||
if ((n % 10) == 1 && (n % 100) != 11) {
|
||||
return 'one';
|
||||
}
|
||||
if ((n % 10) >= 2 && (n % 10) <= 4 &&
|
||||
((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) {
|
||||
return 'few';
|
||||
}
|
||||
if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) ||
|
||||
((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) {
|
||||
// return 'many';
|
||||
return 'other'; // TODO should be "many" but is not defined in translations
|
||||
}
|
||||
return 'other';
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
MessageFormat.locale.ur = function ( n ) {
|
||||
if ( n === 1 ) {
|
||||
return "one";
|
||||
}
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.vi = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.zh = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.zh_CN = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
MessageFormat.locale.zh_TW = function ( n ) {
|
||||
return "other";
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
(function () {
|
||||
I18n.messageFormat = function (key, options) {
|
||||
var fn = I18n._compiledMFs[key];
|
||||
if (fn) {
|
||||
try {
|
||||
return fn(options);
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
}
|
||||
} else {
|
||||
return "Missing Key: " + key;
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -117,14 +117,14 @@ module JsLocaleHelper
|
|||
@loaded_merges = nil
|
||||
end
|
||||
|
||||
def self.translations_for(locale_str)
|
||||
def self.translations_for(locale_str, no_fallback: false)
|
||||
clear_cache! if Rails.env.development?
|
||||
|
||||
locale_sym = locale_str.to_sym
|
||||
|
||||
translations =
|
||||
I18n.with_locale(locale_sym) do
|
||||
if locale_sym == :en
|
||||
if locale_sym == :en || no_fallback
|
||||
load_translations(locale_sym)
|
||||
else
|
||||
load_translations_merged(*I18n.fallbacks[locale_sym])
|
||||
|
@ -134,14 +134,43 @@ module JsLocaleHelper
|
|||
Marshal.load(Marshal.dump(translations))
|
||||
end
|
||||
|
||||
def self.output_MF(locale)
|
||||
require "messageformat"
|
||||
|
||||
message_formats =
|
||||
I18n.fallbacks[locale]
|
||||
.each_with_object({}) do |l, hash|
|
||||
translations = translations_for(l, no_fallback: true)
|
||||
hash[l.to_s.dasherize] = remove_message_formats!(translations, l).merge(
|
||||
TranslationOverride
|
||||
.mf_locales(l)
|
||||
.pluck(:translation_key, :value)
|
||||
.to_h
|
||||
.transform_keys { _1.sub(/^[a-z_]*js\./, "") },
|
||||
)
|
||||
end
|
||||
.compact_blank
|
||||
compiled = MessageFormat.compile(message_formats.keys, message_formats)
|
||||
transpiled = DiscourseJsProcessor.transpile(<<~JS, "", "discourse-mf")
|
||||
import Messages from '@messageformat/runtime/messages';
|
||||
#{compiled.sub("export default", "const msgData =")};
|
||||
const messages = new Messages(msgData, "#{locale.to_s.dasherize}");
|
||||
messages.defaultLocale = "en";
|
||||
globalThis.I18n._mfMessages = messages;
|
||||
JS
|
||||
<<~JS
|
||||
#{transpiled}
|
||||
require("discourse-mf");
|
||||
JS
|
||||
end
|
||||
|
||||
def self.output_locale(locale)
|
||||
locale_str = locale.to_s
|
||||
fallback_locale_str = LocaleSiteSetting.fallback_locale(locale_str)&.to_s
|
||||
translations = translations_for(locale_str)
|
||||
|
||||
message_formats = remove_message_formats!(translations, locale)
|
||||
mf_locale, mf_filename = find_message_format_locale([locale_str], fallback_to_english: true)
|
||||
result = generate_message_format(message_formats, mf_locale, mf_filename)
|
||||
remove_message_formats!(translations, locale)
|
||||
result = +""
|
||||
|
||||
translations.keys.each do |l|
|
||||
translations[l].keys.each { |k| translations[l].delete(k) unless k == "js" }
|
||||
|
@ -153,9 +182,6 @@ module JsLocaleHelper
|
|||
if fallback_locale_str && fallback_locale_str != "en"
|
||||
result << "I18n.fallbackLocale = '#{fallback_locale_str}';\n"
|
||||
end
|
||||
if mf_locale != "en"
|
||||
result << "I18n.pluralizationRules.#{locale_str} = MessageFormat.locale.#{mf_locale};\n"
|
||||
end
|
||||
|
||||
# moment
|
||||
result << File.read("#{Rails.root}/vendor/assets/javascripts/moment.js")
|
||||
|
@ -168,44 +194,24 @@ module JsLocaleHelper
|
|||
end
|
||||
|
||||
def self.output_client_overrides(main_locale)
|
||||
all_overrides = {}
|
||||
has_overrides = false
|
||||
|
||||
I18n.fallbacks[main_locale].each do |locale|
|
||||
overrides =
|
||||
all_overrides[locale] = TranslationOverride
|
||||
.where(locale: locale)
|
||||
.where("translation_key LIKE 'js.%' OR translation_key LIKE 'admin_js.%'")
|
||||
.pluck(:translation_key, :value, :compiled_js)
|
||||
|
||||
has_overrides ||= overrides.present?
|
||||
end
|
||||
|
||||
return "" if !has_overrides
|
||||
|
||||
result = +"I18n._overrides = {};"
|
||||
existing_keys = Set.new
|
||||
message_formats = []
|
||||
|
||||
all_overrides.each do |locale, overrides|
|
||||
translations = {}
|
||||
|
||||
overrides.each do |key, value, compiled_js|
|
||||
next if existing_keys.include?(key)
|
||||
existing_keys << key
|
||||
|
||||
if key.end_with?("_MF")
|
||||
message_formats << "#{key.inspect}: #{compiled_js}"
|
||||
else
|
||||
translations[key] = value
|
||||
locales = I18n.fallbacks[main_locale]
|
||||
all_overrides =
|
||||
locales
|
||||
.each_with_object({}) do |locale, overrides|
|
||||
overrides[locale] = TranslationOverride
|
||||
.client_locales(locale)
|
||||
.pluck(:translation_key, :value)
|
||||
.to_h
|
||||
end
|
||||
end
|
||||
.compact_blank
|
||||
|
||||
result << "I18n._overrides['#{locale}'] = #{translations.to_json};" if translations.present?
|
||||
return "" if all_overrides.blank?
|
||||
|
||||
all_overrides.reduce do |(_, main_overrides), (_, fallback_overrides)|
|
||||
fallback_overrides.slice!(*fallback_overrides.keys - main_overrides.keys)
|
||||
end
|
||||
|
||||
result << "I18n._mfOverrides = {#{message_formats.join(", ")}};"
|
||||
result
|
||||
"I18n._overrides = #{all_overrides.compact_blank.to_json};"
|
||||
end
|
||||
|
||||
def self.output_extra_locales(bundle, locale)
|
||||
|
@ -251,11 +257,6 @@ module JsLocaleHelper
|
|||
end
|
||||
end
|
||||
|
||||
def self.find_message_format_locale(locale_chain, fallback_to_english:)
|
||||
path = "#{Rails.root}/lib/javascripts/locale"
|
||||
find_locale(locale_chain, path, :message_format, fallback_to_english: fallback_to_english)
|
||||
end
|
||||
|
||||
def self.find_locale(locale_chain, path, type, fallback_to_english:)
|
||||
locale_chain.map!(&:to_s)
|
||||
|
||||
|
@ -301,55 +302,6 @@ module JsLocaleHelper
|
|||
filename && File.exist?(filename) ? File.read(filename) << "\n" : ""
|
||||
end
|
||||
|
||||
def self.generate_message_format(message_formats, locale, filename)
|
||||
formats =
|
||||
message_formats
|
||||
.map { |k, v| k.inspect << " : " << compile_message_format(filename, locale, v) }
|
||||
.join(", ")
|
||||
|
||||
result = +"MessageFormat = {locale: {}};\n"
|
||||
result << "I18n._compiledMFs = {#{formats}};\n"
|
||||
result << File.read(filename) << "\n"
|
||||
|
||||
if locale != "en"
|
||||
# Include "en" pluralization rules for use in fallbacks
|
||||
_, en_filename = find_message_format_locale(["en"], fallback_to_english: false)
|
||||
result << File.read(en_filename) << "\n"
|
||||
end
|
||||
|
||||
result << File.read("#{Rails.root}/lib/javascripts/messageformat-lookup.js") << "\n"
|
||||
end
|
||||
|
||||
def self.reset_context
|
||||
@ctx&.dispose
|
||||
@ctx = nil
|
||||
end
|
||||
|
||||
@mutex = Mutex.new
|
||||
def self.with_context
|
||||
@mutex.synchronize do
|
||||
yield(
|
||||
@ctx ||=
|
||||
begin
|
||||
ctx = MiniRacer::Context.new(timeout: 15_000, ensure_gc_after_idle: 2000)
|
||||
ctx.load("#{Rails.root}/node_modules/messageformat/messageformat.js")
|
||||
ctx
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def self.compile_message_format(path, locale, format)
|
||||
with_context do |ctx|
|
||||
ctx.load(path) if File.exist?(path)
|
||||
ctx.eval("mf = new MessageFormat('#{locale}');")
|
||||
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
|
||||
end
|
||||
rescue MiniRacer::EvalError => e
|
||||
message = +"Invalid Format: " << e.message
|
||||
"function(){ return #{message.inspect};}"
|
||||
end
|
||||
|
||||
def self.remove_message_formats!(translations, locale)
|
||||
message_formats = {}
|
||||
I18n.fallbacks[locale]
|
||||
|
|
|
@ -1275,13 +1275,6 @@ class Plugin::Instance
|
|||
locale_chain = opts[:fallbackLocale] ? [locale, opts[:fallbackLocale]] : [locale]
|
||||
lib_locale_path = File.join(root_path, "lib/javascripts/locale")
|
||||
|
||||
path = File.join(lib_locale_path, "message_format")
|
||||
opts[:message_format] = find_locale_file(locale_chain, path)
|
||||
opts[:message_format] = JsLocaleHelper.find_message_format_locale(
|
||||
locale_chain,
|
||||
fallback_to_english: false,
|
||||
) unless opts[:message_format]
|
||||
|
||||
path = File.join(lib_locale_path, "moment_js")
|
||||
opts[:moment_js] = find_locale_file(locale_chain, path)
|
||||
opts[:moment_js] = JsLocaleHelper.find_moment_locale(locale_chain) unless opts[:moment_js]
|
||||
|
@ -1357,8 +1350,7 @@ class Plugin::Instance
|
|||
def valid_locale?(custom_locale)
|
||||
File.exist?(custom_locale[:client_locale_file]) &&
|
||||
File.exist?(custom_locale[:server_locale_file]) &&
|
||||
File.exist?(custom_locale[:js_locale_file]) && custom_locale[:message_format] &&
|
||||
custom_locale[:moment_js]
|
||||
File.exist?(custom_locale[:js_locale_file]) && custom_locale[:moment_js]
|
||||
end
|
||||
|
||||
def find_locale_file(locale_chain, path)
|
||||
|
|
|
@ -55,7 +55,6 @@ task "qunit:test", %i[timeout qunit_path filter] do |_, args|
|
|||
|
||||
begin
|
||||
success = true
|
||||
test_path = "#{Rails.root}/test"
|
||||
qunit_path = args[:qunit_path]
|
||||
filter = args[:filter]
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"@glint/environment-ember-template-imports": "^1.4.0",
|
||||
"@glint/template": "^1.4.0",
|
||||
"@json-editor/json-editor": "2.10.0",
|
||||
"@messageformat/core": "^3.3.0",
|
||||
"@mixer/parallel-prettier": "^2.0.3",
|
||||
"chart.js": "3.5.1",
|
||||
"chartjs-plugin-datalabels": "2.2.0",
|
||||
|
@ -83,4 +84,4 @@
|
|||
"npm": "please-use-yarn",
|
||||
"yarn": ">= 1.21.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,23 +5,27 @@ require "mini_racer"
|
|||
RSpec.describe JsLocaleHelper do
|
||||
let(:v8_ctx) do
|
||||
node_modules = "#{Rails.root}/node_modules/"
|
||||
|
||||
transpiler = DiscourseJsProcessor::Transpiler.new
|
||||
discourse_i18n =
|
||||
transpiler.perform(
|
||||
File.read("#{Rails.root}/app/assets/javascripts/discourse-i18n/src/index.js"),
|
||||
"app/assets/javascripts/discourse",
|
||||
"discourse-i18n",
|
||||
)
|
||||
|
||||
ctx = MiniRacer::Context.new
|
||||
ctx.load("#{node_modules}/loader.js/dist/loader/loader.js")
|
||||
ctx.eval("var window = globalThis;")
|
||||
ctx.eval(discourse_i18n)
|
||||
{
|
||||
"@messageformat/runtime/messages": "#{node_modules}/@messageformat/runtime/esm/messages.js",
|
||||
"@messageformat/runtime": "#{node_modules}/@messageformat/runtime/esm/runtime.js",
|
||||
"@messageformat/runtime/lib/cardinals":
|
||||
"#{node_modules}/@messageformat/runtime/esm/cardinals.js",
|
||||
"make-plural/cardinals": "#{node_modules}/make-plural/cardinals.mjs",
|
||||
"discourse-i18n": "#{Rails.root}/app/assets/javascripts/discourse-i18n/src/index.js",
|
||||
}.each do |module_name, path|
|
||||
ctx.eval(transpiler.perform(File.read(path), "", module_name.to_s))
|
||||
end
|
||||
ctx.eval <<~JS
|
||||
define("discourse/loader-shims", () => {})
|
||||
JS
|
||||
ctx.load("#{Rails.root}/app/assets/javascripts/locales/i18n.js")
|
||||
# As there are circular references in the return value, this raises an
|
||||
# error if we let MiniRacer try to convert the value to JSON. Forcing
|
||||
# returning `null` from `#eval` will prevent that.
|
||||
ctx.eval("#{File.read("#{Rails.root}/app/assets/javascripts/locales/i18n.js")};null")
|
||||
ctx
|
||||
end
|
||||
|
||||
|
@ -54,116 +58,6 @@ RSpec.describe JsLocaleHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe "message format" do
|
||||
def message_format_filename(locale)
|
||||
Rails.root + "lib/javascripts/locale/#{locale}.js"
|
||||
end
|
||||
|
||||
def setup_message_format(format)
|
||||
filename = message_format_filename("en")
|
||||
compiled = JsLocaleHelper.compile_message_format(filename, "en", format)
|
||||
|
||||
@ctx = MiniRacer::Context.new
|
||||
@ctx.eval("MessageFormat = {locale: {}};")
|
||||
@ctx.load(filename)
|
||||
@ctx.eval("var test = #{compiled}")
|
||||
end
|
||||
|
||||
def localize(opts)
|
||||
@ctx.eval("test(#{opts.to_json})")
|
||||
end
|
||||
|
||||
it "handles plurals" do
|
||||
setup_message_format(
|
||||
"{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
}",
|
||||
)
|
||||
expect(localize(NUM_RESULTS: 1)).to eq("1 result")
|
||||
expect(localize(NUM_RESULTS: 2)).to eq("2 results")
|
||||
end
|
||||
|
||||
it "handles double plurals" do
|
||||
setup_message_format(
|
||||
"{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
} and {NUM_APPLES, plural,
|
||||
one {1 apple}
|
||||
other {# apples}
|
||||
}",
|
||||
)
|
||||
|
||||
expect(localize(NUM_RESULTS: 1, NUM_APPLES: 2)).to eq("1 result and 2 apples")
|
||||
expect(localize(NUM_RESULTS: 2, NUM_APPLES: 1)).to eq("2 results and 1 apple")
|
||||
end
|
||||
|
||||
it "handles select" do
|
||||
setup_message_format("{GENDER, select, male {He} female {She} other {They}} read a book")
|
||||
expect(localize(GENDER: "male")).to eq("He read a book")
|
||||
expect(localize(GENDER: "female")).to eq("She read a book")
|
||||
expect(localize(GENDER: "none")).to eq("They read a book")
|
||||
end
|
||||
|
||||
it "can strip out message formats" do
|
||||
hash = { "a" => "b", "c" => { "d" => { "f_MF" => "bob" } } }
|
||||
expect(JsLocaleHelper.strip_out_message_formats!(hash)).to eq("c.d.f_MF" => "bob")
|
||||
expect(hash["c"]["d"]).to eq({})
|
||||
end
|
||||
|
||||
it "handles message format special keys" do
|
||||
JsLocaleHelper.set_translations(
|
||||
"en",
|
||||
"en" => {
|
||||
"js" => {
|
||||
"hello" => "world",
|
||||
"test_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}",
|
||||
"error_MF" => "{{BLA}",
|
||||
"simple_MF" => "{COUNT, plural, one {1} other {#}}",
|
||||
},
|
||||
"admin_js" => {
|
||||
"foo_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
ctx = MiniRacer::Context.new
|
||||
ctx.eval("I18n = { pluralizationRules: {} };")
|
||||
ctx.eval(JsLocaleHelper.output_locale("en"))
|
||||
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["hello"]')).to eq("world")
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["test_MF"]')).to eq(nil)
|
||||
|
||||
expect(ctx.eval('I18n.messageFormat("test_MF", { HELLO: "hi", COUNT: 3 })')).to eq(
|
||||
"hi 3 ducks",
|
||||
)
|
||||
expect(ctx.eval('I18n.messageFormat("error_MF", { HELLO: "hi", COUNT: 3 })')).to match(
|
||||
/Invalid Format/,
|
||||
)
|
||||
expect(ctx.eval('I18n.messageFormat("missing", {})')).to match(/missing/)
|
||||
expect(ctx.eval('I18n.messageFormat("simple_MF", {})')).to match(/COUNT/) # error
|
||||
expect(ctx.eval('I18n.messageFormat("foo_MF", { HELLO: "hi", COUNT: 4 })')).to eq(
|
||||
"hi 4 ducks",
|
||||
)
|
||||
end
|
||||
|
||||
it "load pluralization rules before precompile" do
|
||||
message = JsLocaleHelper.compile_message_format(message_format_filename("ru"), "ru", "format")
|
||||
expect(message).not_to match "Plural Function not found"
|
||||
end
|
||||
|
||||
it "uses message formats from fallback locale" do
|
||||
translations = JsLocaleHelper.translations_for(:en_GB)
|
||||
en_gb_message_formats = JsLocaleHelper.remove_message_formats!(translations, :en_GB)
|
||||
expect(en_gb_message_formats).to_not be_empty
|
||||
|
||||
translations = JsLocaleHelper.translations_for(:en)
|
||||
en_message_formats = JsLocaleHelper.remove_message_formats!(translations, :en)
|
||||
expect(en_gb_message_formats).to eq(en_message_formats)
|
||||
end
|
||||
end
|
||||
|
||||
it "performs fallbacks to English if a translation is not available" do
|
||||
JsLocaleHelper.set_translations(
|
||||
"en",
|
||||
|
@ -235,35 +129,6 @@ RSpec.describe JsLocaleHelper do
|
|||
end
|
||||
end
|
||||
|
||||
it "correctly evaluates message formats in en fallback" do
|
||||
allow_missing_translations do
|
||||
JsLocaleHelper.set_translations("en", "en" => { "js" => { "something_MF" => "en mf" } })
|
||||
JsLocaleHelper.set_translations("de", "de" => { "js" => { "something_MF" => "de mf" } })
|
||||
|
||||
TranslationOverride.upsert!("en", "js.something_MF", <<~MF.strip)
|
||||
There {
|
||||
UNREAD, plural,
|
||||
=0 {are no}
|
||||
one {is one unread}
|
||||
other {are # unread}
|
||||
}
|
||||
MF
|
||||
|
||||
v8_ctx.eval(JsLocaleHelper.output_locale("de"))
|
||||
v8_ctx.eval(JsLocaleHelper.output_client_overrides("de"))
|
||||
v8_ctx.eval(<<~JS)
|
||||
for (let [key, value] of Object.entries(I18n._mfOverrides || {})) {
|
||||
key = key.replace(/^[a-z_]*js\./, "");
|
||||
I18n._compiledMFs[key] = value;
|
||||
}
|
||||
JS
|
||||
|
||||
expect(v8_ctx.eval("I18n.messageFormat('something_MF', { UNREAD: 1 })")).to eq(
|
||||
"There is one unread",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
LocaleSiteSetting.values.each do |locale|
|
||||
it "generates valid date helpers for #{locale[:value]} locale" do
|
||||
js = JsLocaleHelper.output_locale(locale[:value])
|
||||
|
@ -281,31 +146,86 @@ RSpec.describe JsLocaleHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".find_message_format_locale" do
|
||||
it "finds locale's message format rules" do
|
||||
locale, filename =
|
||||
JsLocaleHelper.find_message_format_locale([:de], fallback_to_english: false)
|
||||
expect(locale).to eq("de")
|
||||
expect(filename).to end_with("/de.js")
|
||||
describe ".output_MF" do
|
||||
let(:output) { described_class.output_MF(locale).gsub(/^import.*$/, "") }
|
||||
let(:generated_locales) { v8_ctx.eval("Object.keys(I18n._mfMessages._data)") }
|
||||
let(:translated_message) do
|
||||
v8_ctx.eval("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})")
|
||||
end
|
||||
let!(:overriden_translation) do
|
||||
Fabricate(
|
||||
:translation_override,
|
||||
translation_key: "admin_js.admin.user.penalty_history_MF",
|
||||
value: "OVERRIDEN",
|
||||
)
|
||||
end
|
||||
|
||||
it "finds locale for en_GB" do
|
||||
locale, filename =
|
||||
JsLocaleHelper.find_message_format_locale([:en_GB], fallback_to_english: false)
|
||||
expect(locale).to eq("en")
|
||||
expect(filename).to end_with("/en.js")
|
||||
before { v8_ctx.eval(output) }
|
||||
|
||||
locale, filename =
|
||||
JsLocaleHelper.find_message_format_locale(["en_GB"], fallback_to_english: false)
|
||||
expect(locale).to eq("en")
|
||||
expect(filename).to end_with("/en.js")
|
||||
context "when locale is 'en'" do
|
||||
let(:locale) { "en" }
|
||||
|
||||
it "generates messages for the 'en' locale only" do
|
||||
expect(generated_locales).to eq %w[en]
|
||||
end
|
||||
|
||||
it "translates messages properly" do
|
||||
expect(
|
||||
translated_message,
|
||||
).to eq "This topic has 3 replies with a very high like to post ratio\n"
|
||||
end
|
||||
|
||||
context "when the translation is overriden" do
|
||||
let(:translated_message) do
|
||||
v8_ctx.eval(
|
||||
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
|
||||
)
|
||||
end
|
||||
|
||||
it "returns the overriden translation" do
|
||||
expect(translated_message).to eq "OVERRIDEN"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "falls back to en when locale doesn't have own message format rules" do
|
||||
locale, filename =
|
||||
JsLocaleHelper.find_message_format_locale([:nonexistent], fallback_to_english: true)
|
||||
expect(locale).to eq("en")
|
||||
expect(filename).to end_with("/en.js")
|
||||
context "when locale is not 'en'" do
|
||||
let(:locale) { "fr" }
|
||||
|
||||
it "generates messages for the current locale and uses 'en' as fallback" do
|
||||
expect(generated_locales).to match(%w[fr en])
|
||||
end
|
||||
|
||||
it "translates messages properly" do
|
||||
expect(
|
||||
translated_message,
|
||||
).to eq "Ce sujet comprend 3 réponses avec un taux très élevé de « J'aime » par message\n"
|
||||
end
|
||||
|
||||
context "when a translation is missing" do
|
||||
before { v8_ctx.eval("delete I18n._mfMessages._data.fr.posts_likes_MF") }
|
||||
|
||||
it "returns the fallback translation" do
|
||||
expect(
|
||||
translated_message,
|
||||
).to eq "This topic has 3 replies with a very high like to post ratio\n"
|
||||
end
|
||||
|
||||
context "when the fallback translation is overriden" do
|
||||
let(:translated_message) do
|
||||
v8_ctx.eval(
|
||||
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
v8_ctx.eval("delete I18n._mfMessages._data.fr['admin.user.penalty_history_MF']")
|
||||
end
|
||||
|
||||
it "returns the overriden fallback translation" do
|
||||
expect(translated_message).to eq "OVERRIDEN"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -523,9 +523,6 @@ TEXT
|
|||
expect(DiscoursePluginRegistry.locales).to have_key(:foo_BAR)
|
||||
|
||||
expect(locale[:fallbackLocale]).to be_nil
|
||||
expect(locale[:message_format]).to eq(
|
||||
["foo_BAR", "#{plugin_path}/lib/javascripts/locale/message_format/foo_BAR.js"],
|
||||
)
|
||||
expect(locale[:moment_js]).to eq(
|
||||
["foo_BAR", "#{plugin_path}/lib/javascripts/locale/moment_js/foo_BAR.js"],
|
||||
)
|
||||
|
@ -536,9 +533,6 @@ TEXT
|
|||
|
||||
expect(Rails.configuration.assets.precompile).to include("locales/foo_BAR.js")
|
||||
|
||||
expect(
|
||||
JsLocaleHelper.find_message_format_locale(["foo_BAR"], fallback_to_english: true),
|
||||
).to eq(locale[:message_format])
|
||||
expect(JsLocaleHelper.find_moment_locale(["foo_BAR"])).to eq(locale[:moment_js])
|
||||
expect(JsLocaleHelper.find_moment_locale(["foo_BAR"], timezone_names: true)).to eq(
|
||||
locale[:moment_js_timezones],
|
||||
|
@ -552,9 +546,6 @@ TEXT
|
|||
expect(DiscoursePluginRegistry.locales).to have_key(:tup)
|
||||
|
||||
expect(locale[:fallbackLocale]).to eq("pt_BR")
|
||||
expect(locale[:message_format]).to eq(
|
||||
["pt_BR", "#{Rails.root}/lib/javascripts/locale/pt_BR.js"],
|
||||
)
|
||||
expect(locale[:moment_js]).to eq(
|
||||
["pt-br", "#{Rails.root}/vendor/assets/javascripts/moment-locale/pt-br.js"],
|
||||
)
|
||||
|
@ -565,9 +556,6 @@ TEXT
|
|||
|
||||
expect(Rails.configuration.assets.precompile).to include("locales/tup.js")
|
||||
|
||||
expect(JsLocaleHelper.find_message_format_locale(["tup"], fallback_to_english: true)).to eq(
|
||||
locale[:message_format],
|
||||
)
|
||||
expect(JsLocaleHelper.find_moment_locale(["tup"])).to eq(locale[:moment_js])
|
||||
end
|
||||
|
||||
|
@ -578,9 +566,6 @@ TEXT
|
|||
expect(DiscoursePluginRegistry.locales).to have_key(:tlh)
|
||||
|
||||
expect(locale[:fallbackLocale]).to be_nil
|
||||
expect(locale[:message_format]).to eq(
|
||||
["tlh", "#{plugin_path}/lib/javascripts/locale/message_format/tlh.js"],
|
||||
)
|
||||
expect(locale[:moment_js]).to eq(
|
||||
["tlh", "#{Rails.root}/vendor/assets/javascripts/moment-locale/tlh.js"],
|
||||
)
|
||||
|
@ -588,9 +573,6 @@ TEXT
|
|||
|
||||
expect(Rails.configuration.assets.precompile).to include("locales/tlh.js")
|
||||
|
||||
expect(JsLocaleHelper.find_message_format_locale(["tlh"], fallback_to_english: true)).to eq(
|
||||
locale[:message_format],
|
||||
)
|
||||
expect(JsLocaleHelper.find_moment_locale(["tlh"])).to eq(locale[:moment_js])
|
||||
end
|
||||
|
||||
|
@ -602,7 +584,6 @@ TEXT
|
|||
%w[
|
||||
config/locales/client.foo_BAR.yml
|
||||
config/locales/server.foo_BAR.yml
|
||||
lib/javascripts/locale/message_format/foo_BAR.js
|
||||
lib/javascripts/locale/moment_js/foo_BAR.js
|
||||
assets/locales/foo_BAR.js.erb
|
||||
].each do |path|
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue