// Translator
// Take the given plural text and count to decide on what plural option
// to use, Similar to laravel's trans_choice function but instead
-// takes the direction directly instead of a translation key.
-window.trans_plural(translationString, count, replacements);
+// takes the translation text directly instead of a translation key.
+window.$trans.choice(translationString, count, replacements);
// Component System
// Parse and initialise any components from the given root el down.
import {EventManager} from './services/events.ts';
import {HttpManager} from './services/http.ts';
-import Translations from './services/translations';
+import {Translator} from './services/translations.ts';
import * as componentMap from './components';
import {ComponentStore} from './services/components.ts';
return import(importPath);
};
-// Set events and http services on window
+// Set events, http & translation services on window
window.$http = new HttpManager();
window.$events = new EventManager();
-
-// Translation setup
-// Creates a global function with name 'trans' to be used in the same way as the Laravel translation system
-const translator = new Translations();
-window.trans = translator.get.bind(translator);
-window.trans_choice = translator.getPlural.bind(translator);
-window.trans_plural = translator.parsePlural.bind(translator);
+window.$trans = new Translator();
// Load & initialise components
window.$components = new ComponentStore();
this.showLoading();
await window.$http.delete(`/comment/${this.commentId}`);
+ this.$emit('delete');
this.container.closest('.comment-branch').remove();
window.$events.success(this.deletedText);
- this.$emit('delete');
}
showLoading() {
setupListeners() {
this.elem.addEventListener('page-comment-delete', () => {
- this.updateCount();
+ setTimeout(() => this.updateCount(), 1);
this.hideForm();
});
window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
const newElem = htmlToDom(resp.data);
- this.formContainer.after(newElem);
+
+ if (reqData.parent_id) {
+ this.formContainer.after(newElem);
+ } else {
+ this.container.append(newElem);
+ }
+
window.$events.success(this.createdText);
this.hideForm();
this.updateCount();
updateCount() {
const count = this.getCommentCount();
- this.commentsTitle.textContent = window.trans_plural(this.countText, count, {count});
+ console.log('update count', count, this.container);
+ this.commentsTitle.textContent = window.$trans.choice(this.countText, count, {count});
}
resetForm() {
import {ComponentStore} from "./services/components";
import {EventManager} from "./services/events";
import {HttpManager} from "./services/http";
+import {Translator} from "./services/translations";
declare global {
const __DEV__: boolean;
interface Window {
$components: ComponentStore;
$events: EventManager;
+ $trans: Translator;
$http: HttpManager;
baseUrl: (path: string) => string;
}
--- /dev/null
+import {Translator} from "../translations";
+
+
+describe('Translations Service', () => {
+
+ let $trans: Translator;
+
+ beforeEach(() => {
+ $trans = new Translator();
+ });
+
+ describe('choice()', () => {
+
+ test('it pluralises as expected', () => {
+
+ const cases = [
+ {
+ translation: `cat`, count: 10000,
+ expected: `cat`,
+ },
+ {
+ translation: `cat|cats`, count: 1,
+ expected: `cat`,
+ },
+ {
+ translation: `cat|cats`, count: 0,
+ expected: `cats`,
+ },
+ {
+ translation: `cat|cats`, count: 2,
+ expected: `cats`,
+ },
+ {
+ translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 0,
+ expected: `cat`,
+ },
+ {
+ translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 40,
+ expected: `dog`,
+ },
+ {
+ translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 101,
+ expected: `turtle`,
+ },
+ ];
+
+ for (const testCase of cases) {
+ const output = $trans.choice(testCase.translation, testCase.count, {});
+ expect(output).toEqual(testCase.expected);
+ }
+ });
+
+ test('it replaces as expected', () => {
+ const caseA = $trans.choice(`{0} cat|[1,100] :count dog|[100,*] turtle`, 4, {count: '5'});
+ expect(caseA).toEqual('5 dog');
+
+ const caseB = $trans.choice(`an :a :b :c dinosaur|many`, 1, {a: 'orange', b: 'angry', c: 'big'});
+ expect(caseB).toEqual('an orange angry big dinosaur');
+ });
+
+ test('not provided replacements are left as-is', () => {
+ const caseA = $trans.choice(`An :a dog`, 5, {});
+ expect(caseA).toEqual('An :a dog');
+ });
+
+ });
+});
\ No newline at end of file
+++ /dev/null
-/**
- * Translation Manager
- * Handles the JavaScript side of translating strings
- * in a way which fits with Laravel.
- */
-class Translator {
-
- constructor() {
- this.store = new Map();
- this.parseTranslations();
- }
-
- /**
- * Parse translations out of the page and place into the store.
- */
- parseTranslations() {
- const translationMetaTags = document.querySelectorAll('meta[name="translation"]');
- for (const tag of translationMetaTags) {
- const key = tag.getAttribute('key');
- const value = tag.getAttribute('value');
- this.store.set(key, value);
- }
- }
-
- /**
- * Get a translation, Same format as Laravel's 'trans' helper
- * @param key
- * @param replacements
- * @returns {*}
- */
- get(key, replacements) {
- const text = this.getTransText(key);
- return this.performReplacements(text, replacements);
- }
-
- /**
- * Get pluralised text, Dependent on the given count.
- * Same format at Laravel's 'trans_choice' helper.
- * @param key
- * @param count
- * @param replacements
- * @returns {*}
- */
- getPlural(key, count, replacements) {
- const text = this.getTransText(key);
- return this.parsePlural(text, count, replacements);
- }
-
- /**
- * Parse the given translation and find the correct plural option
- * to use. Similar format at Laravel's 'trans_choice' helper.
- * @param {String} translation
- * @param {Number} count
- * @param {Object} replacements
- * @returns {String}
- */
- parsePlural(translation, count, replacements) {
- const splitText = translation.split('|');
- const exactCountRegex = /^{([0-9]+)}/;
- const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
- let result = null;
-
- for (const t of splitText) {
- // Parse exact matches
- const exactMatches = t.match(exactCountRegex);
- if (exactMatches !== null && Number(exactMatches[1]) === count) {
- result = t.replace(exactCountRegex, '').trim();
- break;
- }
-
- // Parse range matches
- const rangeMatches = t.match(rangeRegex);
- if (rangeMatches !== null) {
- const rangeStart = Number(rangeMatches[1]);
- if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
- result = t.replace(rangeRegex, '').trim();
- break;
- }
- }
- }
-
- if (result === null && splitText.length > 1) {
- result = (count === 1) ? splitText[0] : splitText[1];
- }
-
- if (result === null) {
- result = splitText[0];
- }
-
- return this.performReplacements(result, replacements);
- }
-
- /**
- * Fetched translation text from the store for the given key.
- * @param key
- * @returns {String|Object}
- */
- getTransText(key) {
- const value = this.store.get(key);
-
- if (value === undefined) {
- console.warn(`Translation with key "${key}" does not exist`);
- }
-
- return value;
- }
-
- /**
- * Perform replacements on a string.
- * @param {String} string
- * @param {Object} replacements
- * @returns {*}
- */
- performReplacements(string, replacements) {
- if (!replacements) return string;
- const replaceMatches = string.match(/:(\S+)/g);
- if (replaceMatches === null) return string;
- let updatedString = string;
-
- replaceMatches.forEach(match => {
- const key = match.substring(1);
- if (typeof replacements[key] === 'undefined') return;
- updatedString = updatedString.replace(match, replacements[key]);
- });
-
- return updatedString;
- }
-
-}
-
-export default Translator;
--- /dev/null
+/**
+ * Translation Manager
+ * Helps with some of the JavaScript side of translating strings
+ * in a way which fits with Laravel.
+ */
+export class Translator {
+
+ /**
+ * Parse the given translation and find the correct plural option
+ * to use. Similar format at Laravel's 'trans_choice' helper.
+ */
+ choice(translation: string, count: number, replacements: Record<string, string> = {}): string {
+ const splitText = translation.split('|');
+ const exactCountRegex = /^{([0-9]+)}/;
+ const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
+ let result = null;
+
+ for (const t of splitText) {
+ // Parse exact matches
+ const exactMatches = t.match(exactCountRegex);
+ if (exactMatches !== null && Number(exactMatches[1]) === count) {
+ result = t.replace(exactCountRegex, '').trim();
+ break;
+ }
+
+ // Parse range matches
+ const rangeMatches = t.match(rangeRegex);
+ if (rangeMatches !== null) {
+ const rangeStart = Number(rangeMatches[1]);
+ if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
+ result = t.replace(rangeRegex, '').trim();
+ break;
+ }
+ }
+ }
+
+ if (result === null && splitText.length > 1) {
+ result = (count === 1) ? splitText[0] : splitText[1];
+ }
+
+ if (result === null) {
+ result = splitText[0];
+ }
+
+ return this.performReplacements(result, replacements);
+ }
+
+ protected performReplacements(string: string, replacements: Record<string, string>): string {
+ const replaceMatches = string.match(/:(\S+)/g);
+ if (replaceMatches === null) {
+ return string;
+ }
+
+ let updatedString = string;
+
+ for (const match of replaceMatches) {
+ const key = match.substring(1);
+ if (typeof replacements[key] === 'undefined') {
+ continue;
+ }
+ updatedString = updatedString.replace(match, replacements[key]);
+ }
+
+ return updatedString;
+ }
+
+}