Skip to content

Commit d462c1f

Browse files
feat(crowdin)!: add config options (#98)
1 parent 5eb12b9 commit d462c1f

File tree

2 files changed

+232
-44
lines changed

2 files changed

+232
-44
lines changed

src/js/crowdin.js

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,109 @@
11
const loadScript = require('./load-script');
22

3-
loadScript('https://proxy-translator.app.crowdin.net/assets/proxy-translator.js', function() {
4-
window.proxyTranslator.init({
5-
baseUrl: "https://app.lizardbyte.dev",
6-
appUrl: "https://proxy-translator.app.crowdin.net",
7-
valuesParams: "U2FsdGVkX19ClOT7gAzJBnfQPMcvqkPxFWxyPrnN7UEMztYDAOA+/W4kvMqRz2gDLv2/K3hTEtwbcq5imv5k5MUAykz0uklBcdH49kqeo1AhYpNKXdwXZNYBPXmNUm5F",
8-
distributionBaseUrl: "https://distributions.crowdin.net",
9-
filePath: "/app.lizardbyte.dev.json",
10-
distribution: "0913bb75b61f0b26247ffa91bw4",
11-
languagesData: {
12-
"fr": {"code":"fr","name":"French","twoLettersCode":"fr"},
13-
"es-ES": {"code":"es-ES","name":"Spanish","twoLettersCode":"es"},
14-
"de": {"code":"de","name":"German","twoLettersCode":"de"},
15-
"it": {"code":"it","name":"Italian","twoLettersCode":"it"},
3+
/**
4+
* Initializes Crowdin translation widget based on project and UI platform.
5+
* @param {string} project - Project name ('LizardByte' or 'LizardByte-docs')
6+
* @param {string|null} platform - UI platform ('bootstrap', 'sphinx', or null)
7+
*/
8+
function initCrowdIn(project = 'LizardByte', platform = 'bootstrap') {
9+
// Input validation
10+
if (!['LizardByte', 'LizardByte-docs'].includes(project)) {
11+
console.error('Invalid project. Must be "LizardByte" or "LizardByte-docs"');
12+
return;
13+
}
14+
if (!['bootstrap', 'sphinx', null].includes(platform)) {
15+
console.error('Invalid UI. Must be "bootstrap", "sphinx", or null');
16+
return;
17+
}
18+
19+
loadScript('https://proxy-translator.app.crowdin.net/assets/proxy-translator.js', function() {
20+
// Configure base settings based on project
21+
const projectSettings = {
22+
'LizardByte': {
23+
baseUrl: "https://app.lizardbyte.dev",
24+
valuesParams: "U2FsdGVkX193b3LJT2/HWNIVSb3D61klmnbJ+dvGjoY2XSu35S3gL3FRLBfiXVk4nRsFlfzaC0R7JrklvnS7Xqz5im/VrO+sGzo3LbebxNIMp8LZe28udpnJcA2I2u8B",
25+
filePath: "/app.lizardbyte.dev.json",
26+
distribution: "0913bb75b61f0b26247ffa91bw4",
27+
},
28+
'LizardByte-docs': {
29+
baseUrl: "https://docs.lizardbyte.dev",
30+
valuesParams: "U2FsdGVkX19eQczbrFgaLYbrEBP8is5CVpC2YSnXxH/sRjWqaBtQOsLZJbSRMepcn3D2sofzZxALb2pvT3MLmM+WG5EpWSF7CzzYsAOJ+k/FpMUJ1PZ1FQmmlKCIWyD7",
31+
filePath: "/docs.lizardbyte.dev.json",
32+
distribution: "fb3b3d5c18de9bc717d96b91bw4",
33+
}
34+
};
35+
36+
let languagesData = {
37+
"bg":{"code":"bg","name":"Bulgarian","twoLettersCode":"bg"},
38+
"de":{"code":"de","name":"German","twoLettersCode":"de"},
39+
"en":{"code":"en","name":"English","twoLettersCode":"en"},
40+
"en-GB":{"code":"en-GB","name":"English, United Kingdom","twoLettersCode":"en"},
41+
"en-US":{"code":"en-US","name":"English, United States","twoLettersCode":"en"},
42+
"es-ES":{"code":"es-ES","name":"Spanish","twoLettersCode":"es"},
43+
"fr":{"code":"fr","name":"French","twoLettersCode":"fr"},
44+
"it":{"code":"it","name":"Italian","twoLettersCode":"it"},
1645
"ja":{"code":"ja","name":"Japanese","twoLettersCode":"ja"},
46+
"ko":{"code":"ko","name":"Korean","twoLettersCode":"ko"},
47+
"pl":{"code":"pl","name":"Polish","twoLettersCode":"pl"},
48+
"pt-BR":{"code":"pt-BR","name":"Portuguese, Brazilian","twoLettersCode":"pt"},
1749
"pt-PT":{"code":"pt-PT","name":"Portuguese","twoLettersCode":"pt"},
18-
"ru": {"code":"ru","name":"Russian","twoLettersCode":"ru"},
50+
"ru":{"code":"ru","name":"Russian","twoLettersCode":"ru"},
1951
"sv-SE":{"code":"sv-SE","name":"Swedish","twoLettersCode":"sv"},
2052
"tr":{"code":"tr","name":"Turkish","twoLettersCode":"tr"},
53+
"uk":{"code":"uk","name":"Ukrainian","twoLettersCode":"uk"},
2154
"zh-CN":{"code":"zh-CN","name":"Chinese Simplified","twoLettersCode":"zh"},
22-
"en": {"code":"en","name":"English","twoLettersCode":"en"},
23-
"en-US": {"code":"en-US","name":"English, United States","twoLettersCode":"en"},
24-
"en-GB": {"code":"en-GB","name":"English, United Kingdom","twoLettersCode":"en"}
25-
},
26-
defaultLanguage: "en",
27-
defaultLanguageTitle: "English",
28-
languageDetectType: "default",
29-
poweredBy: false,
30-
position: "bottom-left",
31-
submenuPosition: "top-left",
55+
"zh-TW":{"code":"zh-TW","name":"Chinese Traditional","twoLettersCode":"zh"},
56+
};
57+
// sort languages by name
58+
languagesData = Object.fromEntries(Object.entries(languagesData).sort((a, b) => a[1].name.localeCompare(b[1].name)));
59+
60+
// Initialize Crowdin translator
61+
window.proxyTranslator.init({
62+
baseUrl: projectSettings[project].baseUrl,
63+
appUrl: "https://proxy-translator.app.crowdin.net",
64+
valuesParams: projectSettings[project].valuesParams,
65+
distributionBaseUrl: "https://distributions.crowdin.net",
66+
filePath: projectSettings[project].filePath,
67+
distribution: projectSettings[project].distribution,
68+
distributionSeparateFiles: undefined,
69+
languagesData: languagesData,
70+
defaultLanguage: "en",
71+
defaultLanguageTitle: "English",
72+
languageDetectType: "default",
73+
poweredBy: false,
74+
position: "bottom-left",
75+
submenuPosition: "top-left",
76+
});
77+
78+
// Apply styling based on UI framework
79+
if (platform === null) {
80+
return;
81+
}
82+
setTimeout(() => {
83+
const container = document.getElementById('crowdin-language-picker');
84+
const button = document.getElementsByClassName('cr-picker-button')[0];
85+
const menu = document.getElementsByClassName('cr-picker-submenu')[0];
86+
const selected = document.getElementsByClassName('cr-selected')[0];
87+
88+
if (platform === 'bootstrap') {
89+
button.classList.add('border-white', 'btn', 'btn-outline-light', 'bg-dark', 'text-white', 'rounded-0');
90+
menu.classList.add('border-white', 'bg-dark', 'text-white', 'rounded-0');
91+
selected.classList.add('text-white');
92+
} else if (platform === 'sphinx') {
93+
container.classList.remove('cr-position-bottom-left')
94+
container.style.width = button.offsetWidth + 10 + 'px';
95+
container.style.position = 'relative';
96+
container.style.left = '10px';
97+
container.style.bottom = '10px';
98+
99+
// get rst versions
100+
const sidebar = document.getElementsByClassName('sidebar-sticky')[0];
101+
102+
// move button to related pages
103+
sidebar.appendChild(container);
104+
}
105+
}, 500); // Short delay to ensure elements are available
32106
});
107+
}
33108

34-
// change styling of language selector button
35-
let button = document.getElementsByClassName('cr-picker-button')[0]
36-
button.classList.add('border-white')
37-
button.classList.add('btn')
38-
button.classList.add('btn-outline-light')
39-
button.classList.add('bg-dark')
40-
button.classList.add('text-white')
41-
button.classList.add('rounded-0')
42-
43-
// change styling of language selector menu
44-
let menu = document.getElementsByClassName('cr-picker-submenu')[0]
45-
menu.classList.add('border-white')
46-
menu.classList.add('bg-dark')
47-
menu.classList.add('text-white')
48-
menu.classList.add('rounded-0')
49-
50-
// change styling of selected language in menu
51-
let selected = document.getElementsByClassName('cr-selected')[0]
52-
selected.classList.add('text-white')
53-
});
109+
module.exports = initCrowdIn;

tests/crowdin.test.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {
2+
describe,
3+
expect,
4+
it,
5+
jest,
6+
beforeEach,
7+
afterEach
8+
} from '@jest/globals';
9+
10+
// We need to mock the module BEFORE importing the module that uses it
11+
jest.mock('../src/js/load-script', () => {
12+
return function(url, callback) {
13+
if (callback) setTimeout(callback, 0);
14+
return true;
15+
};
16+
});
17+
18+
const initCrowdIn = require('../src/js/crowdin');
19+
20+
describe('initCrowdIn', () => {
21+
beforeEach(() => {
22+
// Mock DOM elements
23+
global.document.body.innerHTML = `
24+
<div id="crowdin-language-picker">
25+
<div class="cr-picker-button"></div>
26+
<div class="cr-picker-submenu"></div>
27+
<div class="cr-selected"></div>
28+
</div>
29+
30+
<!-- Sphinx sidebar -->
31+
<div class="sidebar-sticky"></div>
32+
`;
33+
34+
// Mock console.error
35+
jest.spyOn(console, 'error').mockImplementation(() => {});
36+
37+
// Mock window.proxyTranslator
38+
global.window.proxyTranslator = {
39+
init: jest.fn()
40+
};
41+
42+
// Use fake timers to control setTimeout
43+
jest.useFakeTimers();
44+
});
45+
46+
afterEach(() => {
47+
jest.clearAllMocks();
48+
jest.useRealTimers();
49+
delete global.window.proxyTranslator;
50+
});
51+
52+
it('should validate project parameter', () => {
53+
initCrowdIn('InvalidProject');
54+
expect(console.error).toHaveBeenCalledWith('Invalid project. Must be "LizardByte" or "LizardByte-docs"');
55+
});
56+
57+
it('should validate platform parameter', () => {
58+
initCrowdIn('LizardByte', 'invalidPlatform');
59+
expect(console.error).toHaveBeenCalledWith('Invalid UI. Must be "bootstrap", "sphinx", or null');
60+
});
61+
62+
it('should initialize proxyTranslator with LizardByte settings', () => {
63+
initCrowdIn();
64+
65+
// Simulate script loading
66+
jest.runAllTimers();
67+
68+
expect(window.proxyTranslator.init).toHaveBeenCalledWith(
69+
expect.objectContaining({
70+
baseUrl: "https://app.lizardbyte.dev",
71+
distribution: "0913bb75b61f0b26247ffa91bw4",
72+
defaultLanguage: "en"
73+
})
74+
);
75+
});
76+
77+
it('should initialize proxyTranslator with LizardByte-docs settings', () => {
78+
initCrowdIn('LizardByte-docs');
79+
80+
// Simulate script loading
81+
jest.runAllTimers();
82+
83+
expect(window.proxyTranslator.init).toHaveBeenCalledWith(
84+
expect.objectContaining({
85+
baseUrl: "https://docs.lizardbyte.dev",
86+
distribution: "fb3b3d5c18de9bc717d96b91bw4",
87+
defaultLanguage: "en"
88+
})
89+
);
90+
});
91+
92+
it('should not apply styling when platform is null', () => {
93+
initCrowdIn('LizardByte', null);
94+
95+
// Simulate script loading and run the setTimeout from UI styling
96+
jest.runAllTimers();
97+
98+
// Verify that no styling was applied
99+
const button = document.getElementsByClassName('cr-picker-button')[0];
100+
expect(button.classList.contains('btn')).toBe(false);
101+
});
102+
103+
it('should apply bootstrap styling', () => {
104+
initCrowdIn('LizardByte', 'bootstrap');
105+
106+
// Simulate script loading and UI styling timeout
107+
jest.runAllTimers();
108+
109+
const button = document.getElementsByClassName('cr-picker-button')[0];
110+
const menu = document.getElementsByClassName('cr-picker-submenu')[0];
111+
const selected = document.getElementsByClassName('cr-selected')[0];
112+
113+
expect(button.classList.contains('btn')).toBe(true);
114+
expect(button.classList.contains('btn-outline-light')).toBe(true);
115+
expect(menu.classList.contains('bg-dark')).toBe(true);
116+
expect(selected.classList.contains('text-white')).toBe(true);
117+
});
118+
119+
it('should apply sphinx styling', () => {
120+
initCrowdIn('LizardByte', 'sphinx');
121+
122+
// Simulate script loading and UI styling timeout
123+
jest.runAllTimers();
124+
125+
const container = document.getElementById('crowdin-language-picker');
126+
const sidebar = document.getElementsByClassName('sidebar-sticky')[0];
127+
128+
expect(container.classList.contains('cr-position-bottom-left')).toBe(false);
129+
expect(container.style.position).toBe('relative');
130+
expect(sidebar.contains(container)).toBe(true);
131+
});
132+
});

0 commit comments

Comments
 (0)