Skip to content

Commit d22413b

Browse files
committed
JS: Converted/updated translation code to TS, fixed some comment counts
- Migrated translation service to TS, stripping a lot of now unused code along the way. - Added test to cover translation service. - Fixed some comment count issues, where it was not showing correct value. or updating, on comment create or delete.
1 parent 8b9bcc1 commit d22413b

File tree

8 files changed

+152
-146
lines changed

8 files changed

+152
-146
lines changed

dev/docs/javascript-code.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ window.$events.showValidationErrors(error);
137137
// Translator
138138
// Take the given plural text and count to decide on what plural option
139139
// to use, Similar to laravel's trans_choice function but instead
140-
// takes the direction directly instead of a translation key.
141-
window.trans_plural(translationString, count, replacements);
140+
// takes the translation text directly instead of a translation key.
141+
window.$trans.choice(translationString, count, replacements);
142142

143143
// Component System
144144
// Parse and initialise any components from the given root el down.

resources/js/app.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {EventManager} from './services/events.ts';
22
import {HttpManager} from './services/http.ts';
3-
import Translations from './services/translations';
3+
import {Translator} from './services/translations.ts';
44
import * as componentMap from './components';
55
import {ComponentStore} from './services/components.ts';
66

@@ -22,16 +22,10 @@ window.importVersioned = function importVersioned(moduleName) {
2222
return import(importPath);
2323
};
2424

25-
// Set events and http services on window
25+
// Set events, http & translation services on window
2626
window.$http = new HttpManager();
2727
window.$events = new EventManager();
28-
29-
// Translation setup
30-
// Creates a global function with name 'trans' to be used in the same way as the Laravel translation system
31-
const translator = new Translations();
32-
window.trans = translator.get.bind(translator);
33-
window.trans_choice = translator.getPlural.bind(translator);
34-
window.trans_plural = translator.parsePlural.bind(translator);
28+
window.$trans = new Translator();
3529

3630
// Load & initialise components
3731
window.$components = new ComponentStore();

resources/js/components/page-comment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ export class PageComment extends Component {
104104
this.showLoading();
105105

106106
await window.$http.delete(`/comment/${this.commentId}`);
107+
this.$emit('delete');
107108
this.container.closest('.comment-branch').remove();
108109
window.$events.success(this.deletedText);
109-
this.$emit('delete');
110110
}
111111

112112
showLoading() {

resources/js/components/page-comments.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class PageComments extends Component {
4040

4141
setupListeners() {
4242
this.elem.addEventListener('page-comment-delete', () => {
43-
this.updateCount();
43+
setTimeout(() => this.updateCount(), 1);
4444
this.hideForm();
4545
});
4646

@@ -72,7 +72,13 @@ export class PageComments extends Component {
7272

7373
window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
7474
const newElem = htmlToDom(resp.data);
75-
this.formContainer.after(newElem);
75+
76+
if (reqData.parent_id) {
77+
this.formContainer.after(newElem);
78+
} else {
79+
this.container.append(newElem);
80+
}
81+
7682
window.$events.success(this.createdText);
7783
this.hideForm();
7884
this.updateCount();
@@ -87,7 +93,8 @@ export class PageComments extends Component {
8793

8894
updateCount() {
8995
const count = this.getCommentCount();
90-
this.commentsTitle.textContent = window.trans_plural(this.countText, count, {count});
96+
console.log('update count', count, this.container);
97+
this.commentsTitle.textContent = window.$trans.choice(this.countText, count, {count});
9198
}
9299

93100
resetForm() {

resources/js/global.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import {ComponentStore} from "./services/components";
22
import {EventManager} from "./services/events";
33
import {HttpManager} from "./services/http";
4+
import {Translator} from "./services/translations";
45

56
declare global {
67
const __DEV__: boolean;
78

89
interface Window {
910
$components: ComponentStore;
1011
$events: EventManager;
12+
$trans: Translator;
1113
$http: HttpManager;
1214
baseUrl: (path: string) => string;
1315
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {Translator} from "../translations";
2+
3+
4+
describe('Translations Service', () => {
5+
6+
let $trans: Translator;
7+
8+
beforeEach(() => {
9+
$trans = new Translator();
10+
});
11+
12+
describe('choice()', () => {
13+
14+
test('it pluralises as expected', () => {
15+
16+
const cases = [
17+
{
18+
translation: `cat`, count: 10000,
19+
expected: `cat`,
20+
},
21+
{
22+
translation: `cat|cats`, count: 1,
23+
expected: `cat`,
24+
},
25+
{
26+
translation: `cat|cats`, count: 0,
27+
expected: `cats`,
28+
},
29+
{
30+
translation: `cat|cats`, count: 2,
31+
expected: `cats`,
32+
},
33+
{
34+
translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 0,
35+
expected: `cat`,
36+
},
37+
{
38+
translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 40,
39+
expected: `dog`,
40+
},
41+
{
42+
translation: `{0} cat|[1,100] dog|[100,*] turtle`, count: 101,
43+
expected: `turtle`,
44+
},
45+
];
46+
47+
for (const testCase of cases) {
48+
const output = $trans.choice(testCase.translation, testCase.count, {});
49+
expect(output).toEqual(testCase.expected);
50+
}
51+
});
52+
53+
test('it replaces as expected', () => {
54+
const caseA = $trans.choice(`{0} cat|[1,100] :count dog|[100,*] turtle`, 4, {count: '5'});
55+
expect(caseA).toEqual('5 dog');
56+
57+
const caseB = $trans.choice(`an :a :b :c dinosaur|many`, 1, {a: 'orange', b: 'angry', c: 'big'});
58+
expect(caseB).toEqual('an orange angry big dinosaur');
59+
});
60+
61+
test('not provided replacements are left as-is', () => {
62+
const caseA = $trans.choice(`An :a dog`, 5, {});
63+
expect(caseA).toEqual('An :a dog');
64+
});
65+
66+
});
67+
});

resources/js/services/translations.js

Lines changed: 0 additions & 131 deletions
This file was deleted.

resources/js/services/translations.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Translation Manager
3+
* Helps with some of the JavaScript side of translating strings
4+
* in a way which fits with Laravel.
5+
*/
6+
export class Translator {
7+
8+
/**
9+
* Parse the given translation and find the correct plural option
10+
* to use. Similar format at Laravel's 'trans_choice' helper.
11+
*/
12+
choice(translation: string, count: number, replacements: Record<string, string> = {}): string {
13+
const splitText = translation.split('|');
14+
const exactCountRegex = /^{([0-9]+)}/;
15+
const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
16+
let result = null;
17+
18+
for (const t of splitText) {
19+
// Parse exact matches
20+
const exactMatches = t.match(exactCountRegex);
21+
if (exactMatches !== null && Number(exactMatches[1]) === count) {
22+
result = t.replace(exactCountRegex, '').trim();
23+
break;
24+
}
25+
26+
// Parse range matches
27+
const rangeMatches = t.match(rangeRegex);
28+
if (rangeMatches !== null) {
29+
const rangeStart = Number(rangeMatches[1]);
30+
if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
31+
result = t.replace(rangeRegex, '').trim();
32+
break;
33+
}
34+
}
35+
}
36+
37+
if (result === null && splitText.length > 1) {
38+
result = (count === 1) ? splitText[0] : splitText[1];
39+
}
40+
41+
if (result === null) {
42+
result = splitText[0];
43+
}
44+
45+
return this.performReplacements(result, replacements);
46+
}
47+
48+
protected performReplacements(string: string, replacements: Record<string, string>): string {
49+
const replaceMatches = string.match(/:(\S+)/g);
50+
if (replaceMatches === null) {
51+
return string;
52+
}
53+
54+
let updatedString = string;
55+
56+
for (const match of replaceMatches) {
57+
const key = match.substring(1);
58+
if (typeof replacements[key] === 'undefined') {
59+
continue;
60+
}
61+
updatedString = updatedString.replace(match, replacements[key]);
62+
}
63+
64+
return updatedString;
65+
}
66+
67+
}

0 commit comments

Comments
 (0)