snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 1s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 1s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
1266
packages/i18n/CHANGELOG.md
Normal file
1266
packages/i18n/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
8
packages/i18n/jest.config.ts
Normal file
8
packages/i18n/jest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import server from '@rocket.chat/jest-presets/server';
|
||||
import type { Config } from 'jest';
|
||||
|
||||
export default {
|
||||
preset: server.preset,
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/@babel', '<rootDir>/node_modules/@jest', 'signal-exit'],
|
||||
collectCoverage: false,
|
||||
} satisfies Config;
|
||||
52
packages/i18n/package.json
Normal file
52
packages/i18n/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@rocket.chat/i18n",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./dist/resources/*": {
|
||||
"default": "./dist/resources/*"
|
||||
},
|
||||
"./dist/languages": {
|
||||
"types": "./dist/languages.d.ts",
|
||||
"import": "./dist/languages.js",
|
||||
"require": "./dist/languages.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc -p tsconfig.build.json && node --experimental-transform-types ./src/scripts/build.mts",
|
||||
"check": "node --experimental-transform-types ./src/scripts/check.mts",
|
||||
"lint": "eslint && node --experimental-transform-types ./src/scripts/check.mts",
|
||||
"lint:fix": "eslint --fix && node --experimental-transform-types ./src/scripts/check.mts --fix",
|
||||
"replace-sprintf": "node --experimental-transform-types ./src/scripts/replace-sprintf.mts",
|
||||
"test": "jest",
|
||||
"testunit": "jest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rocket.chat/jest-presets": "workspace:~",
|
||||
"@rocket.chat/tools": "workspace:~",
|
||||
"@rocket.chat/tsconfig": "workspace:*",
|
||||
"eslint": "~9.39.5",
|
||||
"i18next": "~23.4.9",
|
||||
"jest": "~30.2.0",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rocket.chat/tools": "workspace:~",
|
||||
"i18next": "*"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
2
packages/i18n/src/index.cts
Normal file
2
packages/i18n/src/index.cts
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
module.exports = require('./index.js');
|
||||
151
packages/i18n/src/index.ts
Normal file
151
packages/i18n/src/index.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { isObject } from '@rocket.chat/tools';
|
||||
import type { i18n, TFunction, TOptions } from 'i18next';
|
||||
|
||||
import type { RocketchatI18nKeys } from './resources.ts';
|
||||
|
||||
export type { RocketchatI18nKeys };
|
||||
|
||||
declare module 'i18next' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface TFunction {
|
||||
(key: RocketchatI18nKeys): string;
|
||||
(key: RocketchatI18nKeys, options: TOptions): string;
|
||||
}
|
||||
}
|
||||
|
||||
export const addSprinfToI18n = (t: TFunction) => {
|
||||
return (key: string, ...replaces: any): string => {
|
||||
if (replaces[0] === undefined) {
|
||||
return t(key);
|
||||
}
|
||||
|
||||
if (isObject(replaces[0]) && !Array.isArray(replaces[0])) {
|
||||
return t(key, replaces[0]);
|
||||
}
|
||||
|
||||
return t(key, {
|
||||
postProcess: 'sprintf',
|
||||
sprintf: replaces,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the translation keys from a flat object and group them by namespace
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```js
|
||||
* const source = {
|
||||
* 'core.key1': 'value1',
|
||||
* 'core.key2': 'value2',
|
||||
* 'onboarding.key1': 'value1',
|
||||
* 'onboarding.key2': 'value2',
|
||||
* 'registration.key1': 'value1',
|
||||
* 'registration.key2': 'value2',
|
||||
* 'cloud.key1': 'value1',
|
||||
* 'cloud.key2': 'value2',
|
||||
* 'subscription.key1': 'value1',
|
||||
* 'subscription.key2': 'value2',
|
||||
* };
|
||||
*
|
||||
* const result = extractTranslationNamespaces(source);
|
||||
*
|
||||
* console.log(result);
|
||||
*
|
||||
* // {
|
||||
* // core: {
|
||||
* // key1: 'value1',
|
||||
* // key2: 'value2'
|
||||
* // },
|
||||
* // onboarding: {
|
||||
* // key1: 'value1',
|
||||
* // key2: 'value2'
|
||||
* // },
|
||||
* // registration: {
|
||||
* // key1: 'value1',
|
||||
* // key2: 'value2'
|
||||
* // },
|
||||
* // cloud: {
|
||||
* // key1: 'value1',
|
||||
* // key2: 'value2'
|
||||
* // },
|
||||
* // subscription: {
|
||||
* // key1: 'value1',
|
||||
* // key2: 'value2'
|
||||
* // }
|
||||
* // }
|
||||
* ```
|
||||
*
|
||||
* @param source the flat object with the translation keys
|
||||
*/
|
||||
export const extractTranslationNamespaces = (source: Record<string, string>): Record<TranslationNamespace, Record<string, string>> => {
|
||||
const result: Record<TranslationNamespace, Record<string, string>> = {
|
||||
core: {},
|
||||
onboarding: {},
|
||||
registration: {},
|
||||
cloud: {},
|
||||
subscription: {},
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
const prefix = availableTranslationNamespaces.find((namespace) => key.startsWith(`${namespace}.`));
|
||||
const keyWithoutNamespace = prefix ? key.slice(prefix.length + 1) : key;
|
||||
const ns = prefix ?? defaultTranslationNamespace;
|
||||
result[ns][keyWithoutNamespace] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract only the translation keys that match the given namespaces
|
||||
*
|
||||
* @param source the flat object with the translation keys
|
||||
* @param namespaces the namespaces to extract
|
||||
*/
|
||||
export const extractTranslationKeys = (source: Record<string, string>, namespaces: string | string[] = []): { [key: string]: any } => {
|
||||
const all = extractTranslationNamespaces(source);
|
||||
return Array.isArray(namespaces)
|
||||
? (namespaces as TranslationNamespace[]).reduce((result, namespace) => Object.assign(result, all[namespace]), {})
|
||||
: all[namespaces as TranslationNamespace];
|
||||
};
|
||||
|
||||
export type TranslationNamespace =
|
||||
| (Extract<RocketchatI18nKeys, `${string}.${string}`> extends `${infer T}.${string}` ? (T extends Lowercase<T> ? T : never) : never)
|
||||
| 'core';
|
||||
|
||||
const namespacesMap: Record<TranslationNamespace, true> = {
|
||||
core: true,
|
||||
onboarding: true,
|
||||
registration: true,
|
||||
cloud: true,
|
||||
subscription: true,
|
||||
};
|
||||
|
||||
export const availableTranslationNamespaces = Object.keys(namespacesMap) as TranslationNamespace[];
|
||||
export const defaultTranslationNamespace: TranslationNamespace = 'core';
|
||||
|
||||
export const applyCustomTranslations = (
|
||||
i18n: i18n,
|
||||
parsedCustomTranslations: Record<string, Record<string, string>>,
|
||||
{ namespaces, languages }: { namespaces?: string[]; languages?: string[] } = {},
|
||||
) => {
|
||||
for (const [lng, translations] of Object.entries(parsedCustomTranslations)) {
|
||||
if (languages && !languages.includes(lng)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(translations)) {
|
||||
const prefix = availableTranslationNamespaces.find((namespace) => key.startsWith(`${namespace}.`));
|
||||
const keyWithoutNamespace = prefix ? key.slice(prefix.length + 1) : key;
|
||||
const ns = prefix ?? defaultTranslationNamespace;
|
||||
|
||||
if (namespaces && !namespaces.includes(ns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
i18n.addResourceBundle(lng, ns, { [keyWithoutNamespace]: value }, true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
37
packages/i18n/src/locales/bn-BD.i18n.json
Normal file
37
packages/i18n/src/locales/bn-BD.i18n.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"500": "অভ্যন্তরীণ সার্ভারের ত্রুটি",
|
||||
"@username": "@ইউজারনেইম",
|
||||
"@username_message": "@ইউজারনেইম<message>",
|
||||
"#channel": "#channel",
|
||||
"0_Errors_Only": "০ - ভুল মাত্র",
|
||||
"1_Errors_and_Information": "১ -ত্রুটি ও তথ্য",
|
||||
"12_Hour": "১২-ঘন্টা ঘড়ি",
|
||||
"2_Erros_Information_and_Debug": "২ -ত্রুটি, তথ্য এবং ডিবাগ",
|
||||
"24_Hour": "২৪-ঘন্টা ঘড়ি",
|
||||
"Accept": "গ্রহণ ",
|
||||
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "কোনও অনলাইন এজেন্ট না থাকলেও আগত সার্বজনীন অনুরোধগুলি গ্রহণ করুন",
|
||||
"Accept_new_livechats_when_agent_is_idle": "এজেন্ট যখন নিষ্ক্রিয় থাকে তখন নতুন সার্বজনীন অনুরোধগুলি গ্রহণ করুন",
|
||||
"Accept_with_no_online_agents": "কোনো এজেন্ট অনলাইন না থাকলেও গ্রহণ করুন",
|
||||
"Access_not_authorized": "প্রবেশ অনুমোদিত নয়",
|
||||
"access-mailer": "মেইলারের স্ক্রিন এক্সেস করুন",
|
||||
"access-mailer_description": "সমস্ত ব্যবহারকারীর কাছে গণ ইমেল প্রেরণের অনুমতি",
|
||||
"Accounts": "হিসাব",
|
||||
"Accounts_AllowAnonymousRead": "বেনামে পড়ার অনুমতি দিন",
|
||||
"Accounts_AllowAnonymousWrite": "বেনামে লিখার অনুমতি দিন",
|
||||
"Accounts_AllowDeleteOwnAccount": "ব্যবহারকারীদের নিজস্ব একাউন্ট ডিলিট করার অনুমতি দিন",
|
||||
"Accounts_AllowEmailChange": "ইমেইল পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowEmailNotifications": "ইমেল বিজ্ঞপ্তি অনুমতি দিন",
|
||||
"Accounts_AllowPasswordChange": "পাসওয়ার্ড পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowRealNameChange": "নাম পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowUserAvatarChange": "ব্যবহারকারীর অবতার পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowUsernameChange": "ইউজার নেইম পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowUserProfileChange": "ব্যবহারকারীর প্রোফাইল পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowUserStatusMessageChange": "কাস্টম স্থিতি বার্তার অনুমতি দিন",
|
||||
"Accounts_AvatarBlockUnauthenticatedAccess": "অবতারগুলিতে অযৌক্তিক অ্যাক্সেসটিকে অবরুদ্ধ করুন",
|
||||
"Accounts_ForgetUserSessionOnWindowClose": "উইন্ডো বন্ধে ব্যবহারকারী সেশনটি ভুলে যান",
|
||||
"Edit_Status": "স্থিতি সম্পাদনা করুন",
|
||||
"Filters": "ছাঁকনি",
|
||||
"Home": "বাড়ি",
|
||||
"Login_Logs_UserAgent": "ব্যর্থ লগইন প্রচেষ্টা লগগুলিতে ব্যবহারকারীর এজেন্ট প্রদর্শন করুন",
|
||||
"Looked_for": "অনুসন্ধান করা"
|
||||
}
|
||||
45
packages/i18n/src/locales/bn-IN.i18n.json
Normal file
45
packages/i18n/src/locales/bn-IN.i18n.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"500": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"__count__empty_rooms_will_be_removed_automatically": "{{count}} খালি ঘরগুলি স্বয়ংক্রিয়ভাবে সরানো হবে।",
|
||||
"__count__empty_rooms_will_be_removed_automatically__rooms__": "<bold>{{count}}</bold> খালি ঘরগুলি স্বয়ংক্রিয়ভাবে সরানো হবে: <br/> {{rooms}}।",
|
||||
"@username": "@ব্যবহারকারীর নাম",
|
||||
"@username_message": "@ ব্যবহারকারী নাম <message>",
|
||||
"#channel": "# চ্যানেল",
|
||||
"0_Errors_Only": "0 - শুধুমাত্র ত্রুটি",
|
||||
"1_Errors_and_Information": "1 - ত্রুটি এবং তথ্য",
|
||||
"12_Hour": "12-ঘন্টার ঘড়ি",
|
||||
"2_Erros_Information_and_Debug": "2 - ত্রুটি, তথ্য এবং ডিবাগ",
|
||||
"24_Hour": "24-ঘন্টার ঘড়ি",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__rooms": "একজন নতুন মালিককে <bold>{{count}}</bold> কক্ষে স্বয়ংক্রিয়ভাবে নিয়োগ দেওয়া হবে।",
|
||||
"A_new_owner_will_be_assigned_automatically_to_the__roomName__room": "একজন নতুন মালিককে <bold>{{roomName}}</bold> ঘরে স্বয়ংক্রিয়ভাবে নিয়োগ দেওয়া হবে।",
|
||||
"A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "একজন নতুন মালিককে সেই <bold>{{count}}</bold> কক্ষে স্বয়ংক্রিয়ভাবে নিয়োগ দেওয়া হবে: <br/>{{rooms}}।",
|
||||
"Accept": "গ্রহণ",
|
||||
"Accept_Call": "কল গ্রহণ করুন",
|
||||
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "কোনও এজেন্ট online এ না থাকলেও ইনকামিং ওমনিচ্যানেল অনুরোধগুলি গ্রহণ করুন",
|
||||
"Accept_new_livechats_when_agent_is_idle": "এজেন্ট যখন নিষ্ক্রিয় থাকে তখন নতুন ওমনিচ্যানেল অনুরোধগুলি গ্রহণ করুন",
|
||||
"Accept_with_no_online_agents": "কোনও অনলাইন এজেন্টের সাথে গ্রহণ করুন",
|
||||
"Access_not_authorized": "প্রবেশ অনুমোদিত নয়",
|
||||
"Access_Token_URL": "অ্যাক্সেস টোকেন URL টি",
|
||||
"access-mailer_description": "সকল ব্যবহারকারীর কাছে গণ ইমেল প্রেরণের অনুমতি।",
|
||||
"access-permissions_description": "বিভিন্ন ভূমিকা জন্য অনুমতি পরিবর্তন করুন।",
|
||||
"access-setting-permissions": "সেটিং-ভিত্তিক অনুমতিগুলি পরিবর্তন করুন",
|
||||
"access-setting-permissions_description": "সেটিং-ভিত্তিক অনুমতিগুলি সংশোধন করার অনুমতি",
|
||||
"Account_SID": "অ্যাকাউন্ট এসআইডি",
|
||||
"Accounts": "অ্যাকাউন্ট",
|
||||
"Accounts_AllowDeleteOwnAccount": "ব্যবহারকারীদের নিজস্ব অ্যাকাউন্ট মুছতে অনুমতি দিন",
|
||||
"Accounts_AllowEmailChange": "ইমেল পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowPasswordChange": "পাসওয়ার্ড পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowRealNameChange": "নাম পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_AllowUserProfileChange": "ব্যবহারকারীর প্রোফাইল পরিবর্তনের অনুমতি দিন",
|
||||
"Accounts_Default_User_Preferences": "ডিফল্ট ব্যবহারকারী পছন্দসমূহ",
|
||||
"Accounts_Email_Deactivated_Subject": "অ্যাকাউন্ট নিষ্ক্রিয় করা হয়েছে",
|
||||
"Accounts_Enrollment_Email_Subject_Default": "[Site_Name] এ স্বাগতম",
|
||||
"Accounts_OAuth_Custom_id": "আইডি",
|
||||
"Accounts_OAuth_Custom_Secret": "গোপন",
|
||||
"Accounts_OAuth_Google_secret": "গুগল সিক্রেট",
|
||||
"Add_User": "ব্যবহারকারী যুক্ত করুন",
|
||||
"add-user": "ব্যবহারকারী যুক্ত করুন",
|
||||
"Closed_automatically": "সিস্টেম দ্বারা স্বয়ংক্রিয়ভাবে বন্ধ",
|
||||
"Customize": "কাস্টমাইজ করুন",
|
||||
"This_room_encryption_has_been_disabled_by__username_": "এই ঘরের এনক্রিপশনটি {{username}} দ্বারা বন্ধ করা হয়েছে"
|
||||
}
|
||||
81
packages/i18n/src/locales/et.i18n.json
Normal file
81
packages/i18n/src/locales/et.i18n.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"500": "Sisemine serveri viga",
|
||||
"__username__is_no_longer__role__defined_by__user_by_": "{{username}} ei ole enam {{role}} poolt {{user_by}}",
|
||||
"__username__was_set__role__by__user_by_": "{{username}} määrati {{role}} poolt {{user_by}}",
|
||||
"@username": "@kasutajanimi",
|
||||
"@username_message": "@kasutajanimi<message>",
|
||||
"#channel": "#kanal",
|
||||
"%_of_conversations": "% vestlust",
|
||||
"0_Errors_Only": "0 - Vead ainult",
|
||||
"1_Errors_and_Information": "1 - Veateadet ja informatsioon",
|
||||
"12_Hour": "12 tunni kell",
|
||||
"2_Erros_Information_and_Debug": "2 - Vead, teave ja debug",
|
||||
"24_Hour": "24 tunni kell",
|
||||
"Accept": "Võta vastu",
|
||||
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Võta vastu sissetulevaid livechat taotlusi isegi kui ühtegi agenti pole saadaval",
|
||||
"Accept_with_no_online_agents": "Võtke vastu ilma agentideta",
|
||||
"Access_not_authorized": "Juurdepääs pole lubatud",
|
||||
"Access_Token_URL": "Juurdepääsu token link",
|
||||
"access-mailer_description": "Luba saata mass e-kiri kõikidele kasutajatele.",
|
||||
"access-permissions": "Juurdepääsu õiguste ekraan",
|
||||
"access-permissions_description": "Muutke eri rollide õigusi.",
|
||||
"Accessing_permissions": "Juurdepääs õigustele",
|
||||
"Account_SID": "Konto SID",
|
||||
"Accounts": "Kontod",
|
||||
"Accounts_Admin_Email_Approval_Needed_Default": "<p>Kasutaja <b>[nimi] ([e]) </b> on registreeritud. </p><p>Palun vaadake \"Administration ->Kasutajad\" aktiveerimiseks või kustutamiseks. </p>",
|
||||
"Accounts_Admin_Email_Approval_Needed_Subject_Default": "Uus kasutaja on registreeritud ja vajab kinnitust",
|
||||
"Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "<p>Kasutaja <b>[nimi] ([e]) </b> on registreeritud. </p><p>Põhjus: <b>[põhjus] </b></p><p>Palun vaadake \"Administration ->Kasutajad selle aktiveerimiseks või kustutamiseks. </p>",
|
||||
"Accounts_AllowAnonymousRead": "Luba anonüümne lugemine",
|
||||
"Accounts_AllowAnonymousWrite": "Luba anonüümne kirjutamine",
|
||||
"Accounts_AllowDeleteOwnAccount": "Luba kasutajatel oma konto kustutada",
|
||||
"Accounts_AllowedDomainsList": "Lubatud domeenide loend",
|
||||
"Accounts_AllowedDomainsList_Description": "Komadega eraldatud lubatud domeenide loend",
|
||||
"Accounts_AllowEmailChange": "Luba e-maili muutmine",
|
||||
"Accounts_AllowPasswordChange": "Luba parooli muutmine",
|
||||
"Accounts_AllowRealNameChange": "Lubage nime muutmine",
|
||||
"Accounts_AllowUserAvatarChange": "Lubage kasutajal vahetada avatari",
|
||||
"Accounts_AllowUsernameChange": "Lubage kasutajanime vahetamine",
|
||||
"Accounts_AllowUserProfileChange": "Lubage kasutajal profiili muuta",
|
||||
"Accounts_AvatarCacheTime": "Avatari vahemälu aeg",
|
||||
"Accounts_AvatarCacheTime_description": "Hüperprotokollide sekundite arv, mille jooksul avatari pilte vahemällu hoitakse.",
|
||||
"Additional_Feedback": "Täiendav tagasiside",
|
||||
"Appearance": "Välimus",
|
||||
"Cancel": "Tühista",
|
||||
"Cancel_message_input": "Tühista",
|
||||
"Connected": "Ühendatud",
|
||||
"Edit_Status": "Redigeeri olekut",
|
||||
"How_friendly_was_the_chat_agent": "Kui sõbralik oli sinuga vestelnud agent?",
|
||||
"How_knowledgeable_was_the_chat_agent": "Kui teadlik oli sinuga vestelnud agent? ",
|
||||
"How_responsive_was_the_chat_agent": "Kui vastuvõtlik oli sinuga vestelnud agent? ",
|
||||
"How_satisfied_were_you_with_this_chat": "Kui rahul olite selle vestlusega?",
|
||||
"Installation": "Paigaldamine",
|
||||
"meteor_status_connected": "Ühendatud",
|
||||
"meteor_status_connecting": "Ühendan...",
|
||||
"meteor_status_failed": "Ei saa serveriga ühendust",
|
||||
"meteor_status_offline": "Offline režiimis.",
|
||||
"meteor_status_reconnect_in": {
|
||||
"one": "proovin uuesti 1 sekundi pärast...",
|
||||
"other": "proovin uuesti {{count}} sekundi pärast..."
|
||||
},
|
||||
"meteor_status_try_now_offline": "Ühenda uuesti",
|
||||
"meteor_status_try_now_waiting": "Proovi nüüd",
|
||||
"meteor_status_waiting": "Ootan serveriga ühendust,",
|
||||
"New_messages": "Uued teated",
|
||||
"No": "Ei",
|
||||
"Options": "Valikud",
|
||||
"Please_answer_survey": "Palun võtke hetk, et vastata kiirele küsitlusele toimunud vestluse kohta",
|
||||
"Please_fill_name_and_email": "Palun täitke nimi ja e-posti aadress",
|
||||
"Select_a_department": "Valige osakond",
|
||||
"Select_department": "Valige osakond",
|
||||
"Send": "Saada",
|
||||
"Skip": "Jäta vahele",
|
||||
"Start_Chat": "Alusta vestlust",
|
||||
"Survey": "Uuring",
|
||||
"Thank_you_for_your_feedback": "Aitäh tagasiside eest",
|
||||
"Try_now": "Proovi nüüd",
|
||||
"Type_your_email": "Sisesta oma e-mail",
|
||||
"Type_your_message": "Sisestage oma sõnum",
|
||||
"Type_your_name": "Sisestage oma nimi",
|
||||
"Upload_file_question": "Faili üles laadima?",
|
||||
"User_left": "Kasutaja lahkus"
|
||||
}
|
||||
123
packages/i18n/src/locales/eu.i18n.json
Normal file
123
packages/i18n/src/locales/eu.i18n.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"500": "Zerbitzariaren Barne Errorea",
|
||||
"__username__is_no_longer__role__defined_by__user_by_": "{{username}} is no longer {{role}} by {{user_by}}",
|
||||
"__username__was_set__role__by__user_by_": "{{username}} was set {{role}} by {{user_by}}",
|
||||
"@username": "@erabiltzailea",
|
||||
"@username_message": "@erabiltzailea <message>",
|
||||
"#channel": "#kanala",
|
||||
"0_Errors_Only": "0 - Akatsak Bakarrik",
|
||||
"1_Errors_and_Information": "1 - Akatsak eta Informazioa",
|
||||
"12_Hour": "12 orduko erlojua",
|
||||
"2_Erros_Information_and_Debug": "1 - Akatsak, Informazioa eta Arazketa",
|
||||
"24_Hour": "24 orduko erlojua",
|
||||
"Accept": "Onartu",
|
||||
"Access_not_authorized": "Sarbide ez baimendua",
|
||||
"access-mailer_description": "Erabiltzaile guztiei mezu masiboa bidaltzeko baimena.",
|
||||
"Accessing_permissions": "Sarbide baimenak",
|
||||
"Accounts": "Kontuak",
|
||||
"Accounts_AllowAnonymousRead": "Onartu irakurketa anonimoa",
|
||||
"Accounts_AllowDeleteOwnAccount": "Baimendu erabiltzaileei norbere kontua ezabatzen",
|
||||
"Accounts_AllowedDomainsList": "Onartutako domeinuen zerrenda",
|
||||
"Accounts_AllowedDomainsList_Description": "Komaz bereizitako domeinu baimenduen zerrenda",
|
||||
"Accounts_AllowEmailChange": "Baimendu e-posta aldaketa",
|
||||
"Accounts_AllowPasswordChange": "Baimendu pasahitza aldatzea",
|
||||
"Accounts_AllowRealNameChange": "Baimendu izena aldatzea",
|
||||
"Accounts_AllowUserAvatarChange": "Baimendu Avatar aldaketa",
|
||||
"Accounts_AllowUsernameChange": "Baimendu erabiltzaile-izena aldatzea",
|
||||
"Accounts_AllowUserProfileChange": "Baimendu profil aldaketa",
|
||||
"Accounts_AvatarSize": "Avatar Tamaina",
|
||||
"Accounts_BlockedDomainsList": "Blokeatutako domeinu zerrenda",
|
||||
"Accounts_BlockedDomainsList_Description": "Komaz bereizitako blokeatutako domeinuen zerrenda",
|
||||
"Accounts_CustomFieldsToShowInUserInfo": "Erabiltzaileen informazioan erakusteko eremu pertsonalizatuak",
|
||||
"Accounts_Default_User_Preferences": "Erabiltzailearen hobespen lehenetsiak",
|
||||
"Accounts_Default_User_Preferences_audioNotifications": "Audio jakinarazpen alerta lehenetsia",
|
||||
"Accounts_iframe_enabled": "Gaituta",
|
||||
"Additional_Feedback": "Feedback osagarria",
|
||||
"Administration": "Ezarpenak",
|
||||
"All_channels": "Kanal guztiak",
|
||||
"App_Details": "Aplikazioaren xehetasunak",
|
||||
"App_Information": "Aplikazioen informazioa",
|
||||
"App_Installation": "Aplikazioaren Instalazioa",
|
||||
"App_status_auto_enabled": "Gaituta",
|
||||
"App_status_manually_disabled": "Ezgaituta: eskuz",
|
||||
"App_status_manually_enabled": "Gaituta",
|
||||
"App_status_unknown": "Ezezaguna",
|
||||
"Appearance": "Itxura",
|
||||
"Archive": "Artxiboa",
|
||||
"archive-room": "Artxiboa Room",
|
||||
"archive-room_description": "Kanal bat artxibatzeko baimena",
|
||||
"are_typing": "idazten ari dira",
|
||||
"Are_you_sure": "Ziur zaude?",
|
||||
"Are_you_sure_you_want_to_delete_your_account": "Ziur al zaude zure kontua ezabatu nahi duzula?",
|
||||
"Are_you_sure_you_want_to_disable_Facebook_integration": "Ziur Facebook integrazioa desgaitu nahi duzula?",
|
||||
"Assign_admin": " Admin funtzioa esleitzen",
|
||||
"assign-admin-role": "Esleitu Admin funtzioa",
|
||||
"Attachment_File_Uploaded": "Fitxategia kargatu da",
|
||||
"Attribute_handling": "Atributuen manipulazioa",
|
||||
"Audio": "Audioa",
|
||||
"Audio_message": "Audio mezua",
|
||||
"Audio_Notifications_Default_Alert": "Audio jakinarazpen Alerta lehenetsia",
|
||||
"Auth_Token": "Auth Token",
|
||||
"Author": "Egilea",
|
||||
"Author_Information": "Egilearen informazioa",
|
||||
"Authorize": "Baimendu",
|
||||
"Auto_Load_Images": "Automatikoki kargatu irudiak",
|
||||
"Auto_Translate": "Automatikoki itzuli",
|
||||
"auto-translate": "Automatikoki itzuli",
|
||||
"auto-translate_description": "Itzulpen automatikoa erabiltzeko baimena",
|
||||
"AutoTranslate": "Automatikoki itzuli",
|
||||
"Back": "Atzera",
|
||||
"Back_to_applications": "Aplikazioetara itzuli",
|
||||
"Back_to_chat": "Txatera itzuli",
|
||||
"ban-user": "Erabiltzailea debekatu",
|
||||
"Block_User": "Erabiltzailea blokeatu",
|
||||
"Blockchain": "blockchain",
|
||||
"Cancel": "Ezeztatu",
|
||||
"Cancel_message_input": "Ezeztatu",
|
||||
"CAS_enabled": "Gaituta",
|
||||
"Channels": "Kanalak",
|
||||
"Channels_list": "Kanal publikoen zerrenda",
|
||||
"create-c": "Kanal Publikoak Sortu",
|
||||
"create-p": "Kanal Pribatuak Sortu",
|
||||
"Device_Management_Device_Unknown": "Ezezaguna",
|
||||
"Direct_Messages": "Mezu Zuzenak",
|
||||
"Discussion": "Eztabaida",
|
||||
"Discussion_name": "Eztabaidaren izena",
|
||||
"Discussion_start": "Eztabaida bat hasi",
|
||||
"Discussions": "Eztabaidak",
|
||||
"Edit_Status": "Editatu egoera",
|
||||
"Enabled": "Gaituta",
|
||||
"Federation_Matrix_enabled": "Gaituta",
|
||||
"Group_discussions": "Talde eztabaidak",
|
||||
"How_satisfied_were_you_with_this_chat": "Zein pozik zeuden txat honekin?",
|
||||
"Installation": "Instalazioa",
|
||||
"Integrations_Outgoing_Type_FileUploaded": "Fitxategia kargatu da",
|
||||
"New_messages": "Mezu berriak",
|
||||
"No": "Ez",
|
||||
"No_channels_yet": "Oraindik ez zara inongo kanalen partaide",
|
||||
"No_discussions_yet": "Ez dago eztabaidarik",
|
||||
"onboarding.component.form.action.back": "Atzera",
|
||||
"Options": "Aukerak",
|
||||
"Outlook_Calendar_Enabled": "Gaituta",
|
||||
"Please_fill_name_and_email": "Sartu izena eta posta elektronikoa mesedez",
|
||||
"Private_Groups": "Talde Pribatuak",
|
||||
"Private_Groups_list": "Talde Pribatuen Zerrenda",
|
||||
"RetentionPolicy_Enabled": "Gaituta",
|
||||
"Room_name_changed": "{{user_by}}(e)k mintzagelaren izena {{room_name}}ra aldatu du",
|
||||
"Search_Channels": "Bilatu Kanalak",
|
||||
"Send": "Bidali",
|
||||
"Skip": "Saltatu",
|
||||
"Start_Chat": "Hasi txata",
|
||||
"Stats_Total_Channels": "Kanalak",
|
||||
"Survey": "Inkesta",
|
||||
"Thank_you_for_your_feedback": "Eskerrik asko zure iritziagatik",
|
||||
"Total_Discussions": "Eztabaida guztiak",
|
||||
"Type_your_message": "Idatzi mezua",
|
||||
"Type_your_name": "Idatzi zure izena",
|
||||
"Upload_file_question": "Fitxategia kargatu?",
|
||||
"User_joined_channel": "Kanalera batu da.",
|
||||
"User_left": "Erabiltzailea irten da",
|
||||
"Users": "Erabiltzaileak",
|
||||
"We_are_offline_Sorry_for_the_inconvenience": "Lineaz kanpo gaude. Barkatu eragozpenak.",
|
||||
"Yes": "Bai"
|
||||
}
|
||||
397
packages/i18n/src/locales/gl.i18n.json
Normal file
397
packages/i18n/src/locales/gl.i18n.json
Normal file
@@ -0,0 +1,397 @@
|
||||
{
|
||||
"500": "Erro interno do servidor",
|
||||
"__count__empty_rooms_will_be_removed_automatically": "{{count}} salas vacías serán eliminadas automaticamente.",
|
||||
"__count__empty_rooms_will_be_removed_automatically__rooms__": "Vanse eliminar automaticamente <bold>{{count}}</bold> salas vacías:<br/>{{rooms}}.",
|
||||
"__username__is_no_longer__role__defined_by__user_by_": "{{username}} xa non é {{role}} (por {{user_by}})",
|
||||
"__username__was_set__role__by__user_by_": "{{user_by}} asignou {{role}} a {{username}}",
|
||||
"@username": "@usuario",
|
||||
"@username_message": "@usuario <message>",
|
||||
"#channel": "#canle",
|
||||
"%_of_conversations": "% de conversacións",
|
||||
"0_Errors_Only": "0 - Só erros",
|
||||
"1_Errors_and_Information": "1 - Erros e información",
|
||||
"12_Hour": "Reloxo de 12 horas",
|
||||
"2_Erros_Information_and_Debug": "2 - Erros, información e depuración",
|
||||
"24_Hour": "Reloxo de 24 horas",
|
||||
"A_new_owner_will_be_assigned_automatically_to__count__rooms": "Vaise asignar automaticamente un novo propietario a <bold>{{count}}</bold> salas.",
|
||||
"A_new_owner_will_be_assigned_automatically_to_the__roomName__room": "Vaise asignar automaticamente un novo propietario á sala <bold>{{roomName}}</bold>.",
|
||||
"A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "Vaise asignar automaticamente un novo propietario a <bold>{{count}}</bold> salas:<br/>{{rooms}}.",
|
||||
"Accept": "Aceptar",
|
||||
"Access_not_authorized": "Acceso non autorizado",
|
||||
"Access_Token_URL": "URL de token de acceso",
|
||||
"access-mailer_description": "Permiso para enviar un correo masivo a todos os usuarios.",
|
||||
"access-permissions": "Pantalla de permisos de acceso",
|
||||
"access-permissions_description": "Modificar permisos a varios roles.",
|
||||
"access-setting-permissions": "Modifique os permisos baseados na configuración",
|
||||
"Accessing_permissions": "Accedendo ós permisos",
|
||||
"Account_SID": "SID da conta",
|
||||
"Accounts": "Contas",
|
||||
"Accounts_Admin_Email_Approval_Needed_Subject_Default": "Novo usuario rexistrado que precisa aprobación",
|
||||
"Accounts_AllowAnonymousRead": "Permitir lectura anónima",
|
||||
"Accounts_AllowAnonymousWrite": "Permitir escritura anónima",
|
||||
"Accounts_AllowDeleteOwnAccount": "Permitir ós usuarios eliminar a súa propia conta",
|
||||
"Accounts_AllowedDomainsList": "Lista de dominios permitidos",
|
||||
"Accounts_AllowedDomainsList_Description": "Lista de dominios permitidos separada por comas",
|
||||
"Accounts_AllowEmailChange": "Permitir modificación de correo electrónico",
|
||||
"Accounts_AllowEmailNotifications": "Permitir notificacións por correo electrónico",
|
||||
"Accounts_AllowInvisibleStatusOption": "Permitir opción de estado invisible",
|
||||
"Accounts_AllowPasswordChange": "Permitir modificación de contrasinal",
|
||||
"Accounts_AllowPasswordChangeForOAuthUsers": "Permitir modificación de contrasinal para usuarios de OAuth",
|
||||
"Accounts_AllowRealNameChange": "Permitir modificación de nome",
|
||||
"Accounts_AllowUserAvatarChange": "Permitir ó usuario modificar o avatar",
|
||||
"Accounts_AllowUsernameChange": "Permitir modificación de nome de usuario",
|
||||
"Accounts_AllowUserProfileChange": "Permitir modificación do perfil",
|
||||
"Accounts_AllowUserStatusMessageChange": "Permitir mensaxes de estado personalizados",
|
||||
"Accounts_AvatarBlockUnauthenticatedAccess": "Bloquear o acceso non autenticado aos avatares",
|
||||
"Accounts_AvatarCacheTime": "Tempo de caché de avatar",
|
||||
"Accounts_AvatarCacheTime_description": "Número de segundos que o protocolo http almacena en caché as imaxes do avatar.",
|
||||
"Accounts_AvatarSize": "Tamaño do avatar",
|
||||
"Accounts_BlockedDomainsList": "Lista de dominios bloqueados",
|
||||
"Accounts_BlockedDomainsList_Description": "Lista de dominios bloqueados separados por comas",
|
||||
"Accounts_BlockedUsernameList": "Lista de nomes de usuario bloqueados",
|
||||
"Accounts_BlockedUsernameList_Description": "Lista de nomes de usuario bloqueados separados por comas (sen distinción de maiúsculas e minúsculas)",
|
||||
"Accounts_CustomFieldsToShowInUserInfo": "Campos personalizados para amosar na información do usuario",
|
||||
"Accounts_Default_User_Preferences": "Preferencias de usuario predeterminadas",
|
||||
"Accounts_DefaultUsernamePrefixSuggestion": "Suxestión de prefixo de nome de usuario predeterminado",
|
||||
"Accounts_denyUnverifiedEmail": "Denegar correos electrónicos non verificados",
|
||||
"Accounts_Email_Activated": "[nome] <br/> <br/> <p> A túa conta activouse. </p>",
|
||||
"Accounts_Email_Activated_Subject": "Conta activada",
|
||||
"Accounts_Email_Approved": "[nome] <br/> <br/> <p> A túa conta foi aprobada. </p>",
|
||||
"Accounts_Email_Approved_Subject": "Conta aprobada",
|
||||
"Accounts_Email_Deactivated": "[nome] <br/> <br/> <p> A túa conta desactivouse. </p>",
|
||||
"Accounts_Email_Deactivated_Subject": "Conta desactivada",
|
||||
"Accounts_EmailVerification": "Permitir iniciar sesión só aos usuarios verificados",
|
||||
"Accounts_EmailVerification_Description": "Asegúrese de ter a configuración SMTP correcta para usar esta función",
|
||||
"Accounts_Enrollment_Email": "Correo electrónico de inscrición",
|
||||
"Accounts_Enrollment_Email_Subject_Default": "Benvido a [Site_Name]",
|
||||
"Accounts_ForgetUserSessionOnWindowClose": "Esquece a sesión do usuario ao pechar a xanela",
|
||||
"Accounts_Iframe_api_method": "Método Api",
|
||||
"Accounts_Iframe_api_url": "URL da API",
|
||||
"Accounts_iframe_enabled": "Activado",
|
||||
"Accounts_iframe_url": "URL do iframe",
|
||||
"Accounts_LoginExpiration": "Caducidade do inicio de sesión en días",
|
||||
"Accounts_ManuallyApproveNewUsers": "Aprobar manualmente os novos usuarios",
|
||||
"Accounts_OAuth_Apple": "Inicia sesión con Apple",
|
||||
"Accounts_OAuth_Custom_Access_Token_Param": "Nome do parámetro para o token de acceso",
|
||||
"Accounts_OAuth_Custom_Authorize_Path": "Ruta de autorización",
|
||||
"Accounts_OAuth_Custom_Avatar_Field": "Campo de avatar",
|
||||
"Accounts_OAuth_Custom_Button_Color": "Cor do botón",
|
||||
"Accounts_OAuth_Custom_Button_Label_Color": "Cor do texto do botón",
|
||||
"Accounts_OAuth_Custom_Button_Label_Text": "Texto do botón",
|
||||
"Accounts_OAuth_Custom_Email_Field": "Campo de correo electrónico",
|
||||
"Accounts_OAuth_Custom_Enable": "Activar",
|
||||
"Accounts_OAuth_Custom_id": "Id",
|
||||
"Accounts_OAuth_Custom_Login_Style": "Estilo de inicio de sesión",
|
||||
"Accounts_OAuth_Custom_Merge_Users": "Combinar usuarios",
|
||||
"Accounts_OAuth_Custom_Name_Field": "Campo de nome",
|
||||
"Accounts_OAuth_Custom_Secret": "Segredo",
|
||||
"Accounts_OAuth_Custom_Username_Field": "Campo de nome",
|
||||
"Accounts_OAuth_Drupal": "Habilitado o inicio de sesión de Drupal",
|
||||
"Accounts_OAuth_Drupal_callback_url": "URI de redirección de Drupal oAuth2",
|
||||
"Accounts_OAuth_Wordpress_authorize_path": "Ruta de autorización",
|
||||
"Accounts_OAuth_Wordpress_server_type_custom": "Personalizado",
|
||||
"Accounts_Registration_InviteUrlType_Direct": "Directo",
|
||||
"Activity": "Actividades",
|
||||
"Add_monitor": "Engadir",
|
||||
"All_channels": "Todas as canles",
|
||||
"All_messages": "Todas as mensaxes",
|
||||
"Also_send_to_channel": "Enviar tamén á canle",
|
||||
"Animals_and_Nature": "Animais e natureza",
|
||||
"App_status_auto_enabled": "Activado",
|
||||
"App_status_manually_enabled": "Activado",
|
||||
"Awaiting_confirmation": "Agardando confirmación",
|
||||
"Away": "Ausente",
|
||||
"away": "ausente",
|
||||
"bold": "Grosa",
|
||||
"Browse_Files": "Explorar arquivos",
|
||||
"Busy": "Ocupado",
|
||||
"busy": "ocupado",
|
||||
"Call": "Chamar",
|
||||
"call-management": "Xestión de chamadas",
|
||||
"CAS_enabled": "Activado",
|
||||
"Channel": "Channel",
|
||||
"channel": "canle",
|
||||
"Channel_already_exist": "A canle `#{{channelName}}` xa existe",
|
||||
"Channel_already_exist_static": "A canle xa existe",
|
||||
"Channel_doesnt_exist": "A canle `#{{channelName}}` non existe",
|
||||
"Channel_what_is_this_channel_about": "De que vai esta canle?",
|
||||
"Channels": "Channel",
|
||||
"Channels_list": "Lista de canles públicas",
|
||||
"Chat_close": "Pechar conversa",
|
||||
"Chat_closed": "Conversa pechada",
|
||||
"Chat_Duration": "Duración do chat",
|
||||
"Chat_History": "Historial de conversas",
|
||||
"Choose_messages": "Escoller mensaxes",
|
||||
"Close": "Pechar",
|
||||
"close": "pechar",
|
||||
"Close_chat": "Pechar conversa",
|
||||
"Closed": "Pechado",
|
||||
"cloud.RegisterWorkspace_Setup_Terms_Privacy": "Acepto os <1>Termos e condicións</1> e a <3>Política de privacidade</3>",
|
||||
"Confirm": "Confirmar",
|
||||
"Connection_Closed": "Conexión pechada",
|
||||
"Contact_Center": "Centro de contacto",
|
||||
"Contact_Manager": "Xestor do contacto",
|
||||
"Contact_Name": "Nome de contacto",
|
||||
"Contact_not_found": "Contacto non atopado",
|
||||
"Contact_Profile": "Perfil de contacto",
|
||||
"Contacts": "Contactos",
|
||||
"Create_new": "Crear novo",
|
||||
"Create_new_members": "Crear novo mebro",
|
||||
"Custom": "Personalizado",
|
||||
"Custom_Status": "Estado personalizado",
|
||||
"Custom_User_Status_Info": "Información personalizada do usuario",
|
||||
"Customize": "Personalizar",
|
||||
"Delete_message": "Eliminar mensaxe",
|
||||
"Delete_my_account": "Elimina a miña conta",
|
||||
"delete-d": "Borrar mensaxes directos",
|
||||
"delete-message": "Borrar mensaxe",
|
||||
"Direct": "Directo",
|
||||
"Direct_Message": "Mensaxe directa",
|
||||
"Direct_message": "Mensaxe directa",
|
||||
"Direct_Messages": "Mensaxes directas",
|
||||
"Directory": "Directorio",
|
||||
"Discussion_first_message_title": "A túa mensaxe",
|
||||
"discussion-created": "{{message}}",
|
||||
"Download_Pending_Files": "Descargar ficheiros pendentes",
|
||||
"E2E_enable": "Activa E2E",
|
||||
"Edit_Contact_Profile": "Editar perfil de contacto",
|
||||
"Edit_Status": "Modificar estado",
|
||||
"edit-message": "Editar mensaxe",
|
||||
"Enable": "Activar",
|
||||
"Enable_Auto_Away": "Habilitar auto ausente",
|
||||
"Enabled": "Activado",
|
||||
"Encrypted_message": "Mensaxe cifrada",
|
||||
"error-invalid-message": "Mensaxe inválida",
|
||||
"error-room-is-not-closed": "A sala Room non está pechada",
|
||||
"Export_Messages": "Exportar mensaxes",
|
||||
"Failed_To_Download_Files": "Produciuse un erro ao descargar os ficheiros",
|
||||
"Favorite": "Favorito",
|
||||
"Favorite_Rooms": "Habilitar Room favoritas",
|
||||
"Favorites": "Favoritos",
|
||||
"Federation_Matrix_enabled": "Activado",
|
||||
"File_name_Placeholder": "Procurar arquivos...",
|
||||
"Files": "Lista de arquivos",
|
||||
"files": "arquivos",
|
||||
"Flags": "Bandeiras",
|
||||
"Follow_message": "Seguir mensaxe",
|
||||
"Food_and_Drink": "Comida e Bebida",
|
||||
"For_more_details_please_check_our_docs": "Para máis detalles, consulte os nosos documentos.",
|
||||
"Frequently_Used": "Usados frecuentemente",
|
||||
"Group_favorites": "Favoritos do grupo",
|
||||
"Home": "Inicio",
|
||||
"Importer_importing_files": "Importando os arquivos.",
|
||||
"importer_status_importing_files": "Importando arquivos",
|
||||
"importer_status_preparing_messages": "Lendo arquivos de mensaxes",
|
||||
"importer_status_preparing_started": "Lendo arquivos",
|
||||
"Importer_Upload_Unlimited_FileSize": "A configuración do servidor permite a carga de ficheiros de calquera tamaño.",
|
||||
"inline_code": "código en liña",
|
||||
"Integrations_Outgoing_Type_SendMessage": "Mensaxe enviada",
|
||||
"Invisible": "Oculto",
|
||||
"invisible": "oculto",
|
||||
"Italic": "Cursiva",
|
||||
"Join_audio_call": "Unirse á chamada",
|
||||
"Join_video_call": "Unirse á videochamada",
|
||||
"Jump_to_message": "Ir á mensaxe",
|
||||
"Last_Chat": "Último chat",
|
||||
"Last_Message": "Última mensaxe",
|
||||
"LDAP_Authentication": "Activar",
|
||||
"LDAP_Enable": "Activar",
|
||||
"LDAP_Find_User_After_Login": "Procurar usuario despois do inicio de sesión",
|
||||
"LDAP_Sync_AutoLogout_Enabled": "Habilitar peche de sesión automático",
|
||||
"Livestream_live_now": "En directo!",
|
||||
"Load_more": "Cargar máis",
|
||||
"Loading_more_from_history": "Cargando máis do historial",
|
||||
"Logout": "Pechar sesión",
|
||||
"Logout_Others": "Pechar sesión en outras localizacións",
|
||||
"Message": "Mensaxe",
|
||||
"message": "mensaxe",
|
||||
"Message_deleting_blocked": "Esta mensaxe xa non se pode eliminar",
|
||||
"Message_removed": "Eliminouse a mensaxe",
|
||||
"message_removed": "eliminouse a mensaxe",
|
||||
"Message_too_long": "A mensaxe é demasiado longa",
|
||||
"Messages": "Mensaxes",
|
||||
"messages": "mensaxes",
|
||||
"Messages_sent": "Mensaxes enviadas",
|
||||
"Moderation_Delete_message": "Eliminar mensaxe",
|
||||
"More_channels": "Máis canles",
|
||||
"More_direct_messages": "Máis mensaxes directas",
|
||||
"More_groups": "Máis grupos privados",
|
||||
"More_unreads": "Máis sen ler",
|
||||
"multi_line": "multi liña",
|
||||
"My_Account": "A miña conta",
|
||||
"New_messages": "Novas mensaxes",
|
||||
"No_files_left_to_download": "Non quedan ficheiros para descargar",
|
||||
"No_pinned_messages": "Sen mensaxes fixadas",
|
||||
"No_starred_messages": "Sen mensaxes favoritas",
|
||||
"No_Threads": "Non se atoparon fíos",
|
||||
"Notifications_Preferences": "Preferencias de notificacións",
|
||||
"Objects": "Obxectos",
|
||||
"Offline_Message": "Mensaxe fora de liña",
|
||||
"Offline_message": "Mensaxe fora de liña",
|
||||
"Offline_messages": "Mensaxes fora de liña",
|
||||
"Omnichannel_Reports_Status_Closed": "Pechado",
|
||||
"onboarding.component.emailCodeFallback": "Non recibiches o correo electrónico? <1>Reenviar</1> ou <3>Cambiar correo electrónico</3>",
|
||||
"onboarding.component.form.action.back": "De volta",
|
||||
"onboarding.component.form.action.confirm": "Confirmar",
|
||||
"onboarding.component.form.action.next": "A continuación",
|
||||
"onboarding.component.form.action.register": "Rexistrarse",
|
||||
"onboarding.component.form.action.skip": "Ignore este paso",
|
||||
"onboarding.component.form.requiredField": "Este campo é necesario",
|
||||
"onboarding.component.form.steps": "Paso {{currentStep}} de {{stepCount}}",
|
||||
"onboarding.component.form.termsAndConditions": "Acepto os <1>Termos e condicións</1> e a <3>Política de privacidade</3>",
|
||||
"onboarding.form.adminInfoForm.fields.email.label": "Email",
|
||||
"onboarding.form.adminInfoForm.fields.email.placeholder": "Email",
|
||||
"onboarding.form.adminInfoForm.fields.fullName.label": "Nome completo",
|
||||
"onboarding.form.adminInfoForm.fields.fullName.placeholder": "Nome e apelidos",
|
||||
"onboarding.form.adminInfoForm.fields.keepPosted.label": "Mantéñame informado sobre as actualizacións de Rocket.Chat",
|
||||
"onboarding.form.adminInfoForm.fields.password.label": "Contrasinal",
|
||||
"onboarding.form.adminInfoForm.fields.password.placeholder": "Crear contrasinal",
|
||||
"onboarding.form.adminInfoForm.fields.username.label": "Nome de usuario",
|
||||
"onboarding.form.adminInfoForm.fields.username.placeholder": "@nome-de-usuario",
|
||||
"onboarding.form.adminInfoForm.subtitle": "Necesitamos isto para crear un perfil de administrador dentro do teu espazo de traballo",
|
||||
"onboarding.form.adminInfoForm.title": "Información administrativa",
|
||||
"onboarding.form.awaitConfirmationForm.title": "Agardando confirmación",
|
||||
"onboarding.form.organizationInfoForm.fields.country.label": "País",
|
||||
"onboarding.form.organizationInfoForm.fields.country.placeholder": "Seleccione",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationIndustry.label": "Industria da organización",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationIndustry.placeholder": "Seleccione",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationName.label": "Nome da Organización",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationName.placeholder": "Nome da Organización",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationSize.label": "Tamaño da organización",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationSize.placeholder": "Seleccione",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationType.label": "Tipo de organización",
|
||||
"onboarding.form.organizationInfoForm.fields.organizationType.placeholder": "Seleccione",
|
||||
"onboarding.form.organizationInfoForm.subtitle": "Por favor, ten paciencia connosco. Esta información axudaranos a personalizar o teu espazo de traballo",
|
||||
"onboarding.form.organizationInfoForm.title": "Información da organización",
|
||||
"onboarding.form.registeredServerForm.continueStandalone": "Continúa como autónomo",
|
||||
"onboarding.form.registeredServerForm.fields.accountEmail.inputLabel": "Correo electrónico da conta na nube",
|
||||
"onboarding.form.registeredServerForm.fields.accountEmail.inputPlaceholder": "Introduce o teu correo electrónico",
|
||||
"onboarding.form.registeredServerForm.included.apps": "Acceso a aplicacións do mercado",
|
||||
"onboarding.form.registeredServerForm.included.externalProviders": "Integración con provedores externos (WhatsApp, Facebook, Telegram, Twitter)",
|
||||
"onboarding.form.registeredServerForm.included.push": "Notificacións push móbiles",
|
||||
"onboarding.form.registeredServerForm.keepInformed": "Mantéñame informado sobre novidades e eventos",
|
||||
"onboarding.form.registeredServerForm.title": "Rexistra o teu servidor",
|
||||
"onboarding.form.standaloneServerForm.manuallyIntegrate": "Necesita integrarse manualmente con servizos externos",
|
||||
"onboarding.form.standaloneServerForm.publishOwnApp": "Para enviar notificacións push, debes compilar e publicar a túa propia aplicación en Google Play e App Store",
|
||||
"onboarding.form.standaloneServerForm.servicesUnavailable": "Algúns dos servizos non estarán dispoñibles ou requirirán unha configuración manual",
|
||||
"onboarding.form.standaloneServerForm.title": "Confirmación do servidor autónomo",
|
||||
"onboarding.page.alreadyHaveAccount": "Xa ten unha conta? <1>Xestiona os teus espazos de traballo.</1>",
|
||||
"onboarding.page.awaitingConfirmation.subtitle": "Enviámosche un correo electrónico a {{emailAddress}} cunha ligazón de confirmación. Verifique que o código de seguranza que aparece a continuación coincide co do correo electrónico.",
|
||||
"onboarding.page.checkYourEmail.subtitle": "A túa solicitude enviouse correctamente.<1></1>Comproba a túa caixa de entrada de correo electrónico para iniciar a proba de Enterprise.<1></1>A ligazón caducará en 30 minutos.",
|
||||
"onboarding.page.checkYourEmail.title": "Comprobe o seu correo electrónico",
|
||||
"onboarding.page.cloudDescription.auditing": "Panel de auditoría de mensaxes / Rexistros de auditoría",
|
||||
"onboarding.page.cloudDescription.availability": "Alta dispoñibilidade",
|
||||
"onboarding.page.cloudDescription.engagement": "Panel de control de compromiso",
|
||||
"onboarding.page.cloudDescription.goldIncludes": "* O plano dourado inclúe todas as funcións doutros planos",
|
||||
"onboarding.page.cloudDescription.ldap": "Sincronización mellorada por LDAP",
|
||||
"onboarding.page.cloudDescription.numberOfIntegrations": "1.000 integracións",
|
||||
"onboarding.page.cloudDescription.omnichannel": "Omnichannel premium",
|
||||
"onboarding.page.cloudDescription.push": "Notificacións push seguras",
|
||||
"onboarding.page.cloudDescription.sla": "SLA: Premium",
|
||||
"onboarding.page.cloudDescription.title": "Imos lanzar o teu espazo de traballo e a <1>proba de 14 días</1>",
|
||||
"onboarding.page.cloudDescription.tryGold": "Proba o noso mellor plan Gold durante 14 días de balde",
|
||||
"onboarding.page.confirmationProcess.title": "Confirmación en proceso",
|
||||
"onboarding.page.emailConfirmed.subtitle": "Podes volver á túa aplicación Rocket.Chat: xa lanzamos o teu espazo de traballo.",
|
||||
"onboarding.page.emailConfirmed.title": "Correo electrónico confirmado!",
|
||||
"onboarding.page.form.title": "Imos Iniciar o teu espazo de traballo",
|
||||
"onboarding.page.invalidLink.button.text": "Solicita un novo link",
|
||||
"onboarding.page.invalidLink.content": "Parece que xa usaches a link de invitación. Xérase para un único inicio de sesión. Solicita un novo para unirse ao teu espazo de traballo.",
|
||||
"onboarding.page.invalidLink.title": "A tú link xa non é válido",
|
||||
"onboarding.page.magicLinkEmail.subtitle": "Fai clic na ligazón do correo electrónico que che acabamos de enviar para iniciar sesión no teu espazo de traballo. <1>A ligazón caducará en 30 minutos.</1>",
|
||||
"onboarding.page.magicLinkEmail.title": "Enviámosche por correo electrónico un link de inicio de sesión",
|
||||
"onboarding.page.requestTrial.subtitle": "Proba o noso mellor plana de empresas durante 30 días de balde",
|
||||
"onboarding.page.requestTrial.title": "Solicita unha <1>proba de 30 días</1>",
|
||||
"Online": "En liña",
|
||||
"online": "en liña",
|
||||
"Only_you_can_see_this_message": "So ti podes ver esta mensaxe",
|
||||
"Options": "Opcións",
|
||||
"Outlook_Calendar_Enabled": "Activado",
|
||||
"Phone": "Teléfono",
|
||||
"Pin_Message": "Fixar mensaxe",
|
||||
"pin-message": "Fixar mensaxe",
|
||||
"Pinned_a_message": "Fixouse unha mensaxe:",
|
||||
"Pinned_Messages": "Mensaxes fixadas",
|
||||
"Preferences_saved": "Preferencias gardadas",
|
||||
"Profile_saved_successfully": "Perfil gardado correctamente",
|
||||
"Push_enable": "Activar",
|
||||
"RegisterWorkspace_Features_MobileNotifications_Title": "Notificacións push móbiles",
|
||||
"RetentionPolicy_Enabled": "Activado",
|
||||
"Room_uploaded_file_list": "Lista de ficheiros",
|
||||
"SAML_Custom_Logout_Behaviour": "Comportamento de peche de sesión",
|
||||
"Save": "Gardar",
|
||||
"Save_changes": "Gardar cambios",
|
||||
"Saved": "Gardado",
|
||||
"Search_Files": "Procurar arquivos",
|
||||
"Search_Messages": "Procurar mensaxes",
|
||||
"Select": "Seleccione",
|
||||
"Send_a_message": "Enviar unha mensaxe",
|
||||
"Set_as_favorite": "Marcar como favorito",
|
||||
"Show_only_online": "Mostrar só en liña",
|
||||
"Slash_Gimme_Description": "Mostra ༼ つ ◕_◕ ༽ つ antes da súa mensaxe",
|
||||
"Slash_Shrug_Description": "Mostra ¯ \\ _ (ツ) _ / ¯ despois da súa mensaxe",
|
||||
"Slash_Status_Params": "Mensaxe de estado",
|
||||
"Smileys_and_People": "Sorrisos e persoas",
|
||||
"Star_Message": "Fixar mensaxe",
|
||||
"Starred_Messages": "Mensaxes favoritas",
|
||||
"Start_audio_call": "Iniciar chamada de audio",
|
||||
"Start_video_call": "Iniciar videochamada",
|
||||
"Started_a_video_call": "Iniciou unha videochamada",
|
||||
"Stats_Away_Users": "Usuarios ausentes",
|
||||
"Stats_Online_Users": "Usuarios en liña",
|
||||
"Stats_Total_Channels": "Channel",
|
||||
"Stats_Total_Messages": "Mensaxes",
|
||||
"StatusMessage": "Mensaxe de Estado",
|
||||
"strike": "Tachado",
|
||||
"Symbols": "Símbolos",
|
||||
"theme-color-status-away": "Cor do estado ausente",
|
||||
"theme-color-status-busy": "Cor do estado ocupado",
|
||||
"theme-color-status-online": "Cor do estado en liña",
|
||||
"Threads": "Fíos",
|
||||
"to_see_more_details_on_how_to_integrate": "para ver máis detalles sobre como integrarse.",
|
||||
"Total_Threads": "Fíos totais",
|
||||
"Travel_and_Places": "Viaxes e Lugares",
|
||||
"Type_your_message": "Escribe a túa mensaxe",
|
||||
"Unpin_Message": "Anular fixación da mensaxe",
|
||||
"User_Info": "Información do usuario",
|
||||
"Videocall_declined": "Videochamada rexeitada.",
|
||||
"Videocall_enabled": "Videochamada habilitada",
|
||||
"view-full-other-user-info": "Ver toda a información do usuario",
|
||||
"You_can_close_this_window_now": "Xa podes pechar esta ventá.",
|
||||
"You_can_use_an_emoji_as_avatar": "Tamén podes usar un emoji como avatar.",
|
||||
"You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Podes usar webhooks para integrar facilmente o chat de vídeo co teu CRM.",
|
||||
"You_cant_leave_a_livechat_room_Please_use_the_close_button": "Non podes abandonar unha sala de chat en vivo. Por favor, utiliza o botón de pechar.",
|
||||
"You_have_a_new_message": "Tes unha nova mensaxe",
|
||||
"You_have_been_muted": "Fuches silenciado e non podes falar en esta sala",
|
||||
"You_have_n_codes_remaining": "Tes {{number}} códigos restantes.",
|
||||
"You_have_not_verified_your_email": "Ainda non verificaches ó teu correo electrónico.",
|
||||
"You_have_successfully_unsubscribed": "Décheste de baixa correctamente da nosa lista de distribución de correo.",
|
||||
"You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Tes que enviar primeiro un token de API para utilizar a integración.",
|
||||
"You_must_join_to_view_messages_in_this_channel": "Tes que unirte para ver as mensaxes de esta canle",
|
||||
"You_need_confirm_email": "Tes que confirmar o teu correo electrónico para poder iniciar sesión!",
|
||||
"You_need_install_an_extension_to_allow_screen_sharing": "Tes que instalar unha extensión para permitir a compartición de pantalla",
|
||||
"You_need_to_change_your_password": "Tes que cambiar a contrasinal",
|
||||
"You_need_to_type_in_your_password_in_order_to_do_this": "Tes que escribir a túa contrasinal para poder facer esto!",
|
||||
"You_need_to_type_in_your_username_in_order_to_do_this": "Tes que escribit a túa contrasinal para poder facer esto!",
|
||||
"You_need_to_verifiy_your_email_address_to_get_notications": "Tes que verificar a túa dirección de correo electrónico para recibir notificacións",
|
||||
"You_reached_the_maximum_number_of_guest_users_allowed_by_your_license": "Acadaches a máxima cantidade permitida de invitados para a túa licenza.",
|
||||
"You_should_inform_one_url_at_least": "Debes definir polo menos unha URL.",
|
||||
"You_should_name_it_to_easily_manage_your_integrations": "Debes asignarlle un nome para administrar facilmente as túas integracións.",
|
||||
"You_will_not_be_able_to_recover": "Non poderás recuperar esta mensaxe!",
|
||||
"You_will_not_be_able_to_recover_file": "Non poderás recuperar este arquivo!",
|
||||
"You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "Non vas recibir notificacións por correo electrónico porque non verificaches a túa dirección de correo electrónico.",
|
||||
"Your_email_address_has_changed": "A túa dirección de correo electrónico foi modificada.",
|
||||
"Your_entry_has_been_deleted": "A túa entrada foi eliminada.",
|
||||
"Your_file_has_been_deleted": "O teu arquivo foi eliminado.",
|
||||
"Your_invite_link_will_expire_after__usesLeft__uses": "A túa ligazón de invitación caducará despois de {{usesLeft}} usos.",
|
||||
"Your_invite_link_will_never_expire": "A túa ligazón de invitación non caduca.",
|
||||
"your_message": "a túa mensaxe",
|
||||
"your_message_optional": "a túa mensaxe (opcional)",
|
||||
"Your_new_email_is_email": "A túa nova dirección de correo electrónico é <strong>[email]</strong>.",
|
||||
"Your_password_is_wrong": "A túa contrasinal é errónea!",
|
||||
"Your_password_was_changed_by_an_admin": "A túa contrasinal modificouse por un administrador.",
|
||||
"Your_push_was_sent_to_s_devices": "A notificación push enviouse a %s dispositivos",
|
||||
"Your_question": "A túa pregunta",
|
||||
"Your_server_link": "O teu enlace do servidor",
|
||||
"Your_temporary_password_is_password": "A túa contrasinal temporal é <strong>[password]</strong>.",
|
||||
"Your_workspace_is_ready": "O teu espazo de traballo está preparado para usarse🎉"
|
||||
}
|
||||
1502
packages/i18n/src/locales/he.i18n.json
Normal file
1502
packages/i18n/src/locales/he.i18n.json
Normal file
File diff suppressed because it is too large
Load Diff
213
packages/i18n/src/locales/hi.i18n.json
Normal file
213
packages/i18n/src/locales/hi.i18n.json
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"500": "आंतरिक सर्वर त्रुटि",
|
||||
"__username__is_no_longer__role__defined_by__user_by_": "{{username}} is no longer {{role}} by {{user_by}}",
|
||||
"__username__was_set__role__by__user_by_": "{{username}} was set {{role}} by {{user_by}}",
|
||||
"@username": "@यूज़रनेम",
|
||||
"@username_message": "@यूज़रनेम<message> ",
|
||||
"#channel": "#चैनल",
|
||||
"%_of_conversations": "% बातचीत",
|
||||
"0_Errors_Only": "0 - त्रुटियां केवल",
|
||||
"1_Errors_and_Information": "1 - त्रुटियां और सूचना",
|
||||
"12_Hour": "12-घंटे की घड़ी",
|
||||
"2_Erros_Information_and_Debug": "2 - त्रुटियां, सूचना और डिबग",
|
||||
"24_Hour": "24-घंटे की घड़ी",
|
||||
"Accept": "स्वीकार करें",
|
||||
"Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "यदि कोई ऑनलाइन एजेंट नहीं हैं, तो भी इनकमिंग लाइवचैट अनुरोध स्वीकार करें",
|
||||
"Accept_with_no_online_agents": "कोई ऑनलाइन एजेंटों के साथ स्वीकार करें",
|
||||
"Access_not_authorized": "प्रवेश अधिकृत नहीं है",
|
||||
"Access_Token_URL": "एक्सेस टोकन URL",
|
||||
"access-mailer": "मेलर स्क्रीन एक्सेस करें",
|
||||
"access-mailer_description": "सभी उपयोगकर्ताओं को बड़े पैमाने पर ईमेल भेजने की अनुमति।",
|
||||
"access-permissions": "अनुमतियाँ स्क्रीन एक्सेस करें",
|
||||
"access-permissions_description": "विभिन्न भूमिकाओं के लिए अनुमतियों को संशोधित करें।",
|
||||
"Accessing_permissions": "अक्सेस्सिंग की अनुमति",
|
||||
"Account_SID": "खाता एसआईडी",
|
||||
"Accounts": "खाता",
|
||||
"Accounts_Admin_Email_Approval_Needed_Default": "<p>The user <b>[name] ([email])</b> has been registered.</p><p>Please check \"Administration -> Users\" to activate or delete it.</p>",
|
||||
"Accounts_Admin_Email_Approval_Needed_Subject_Default": "एक नया उपयोगकर्ता पंजीकृत है और उसे अनुमोदन की आवश्यकता है",
|
||||
"Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "<p>The user <b>[name] ([email])</b> has been registered.</p><p>Reason: <b>[reason]</b></p><p>Please check \"Administration -> Users\" to activate or delete it.</p>",
|
||||
"Accounts_AllowAnonymousRead": "अनाम पढ़ने की अनुमति दें",
|
||||
"Accounts_AllowAnonymousWrite": "अनाम लिखने की अनुमति दें",
|
||||
"Accounts_AllowDeleteOwnAccount": "उपयोगकर्ताओं को स्वयं का खाता हटाने की अनुमति दें",
|
||||
"Accounts_AllowedDomainsList": "अनुमत डोमेन सूची",
|
||||
"Accounts_AllowedDomainsList_Description": "अनुमत डोमेन की कोमा-पृथक सूची",
|
||||
"Accounts_AllowEmailChange": "ईमेल परिवर्तन की अनुमति दें",
|
||||
"Accounts_AllowPasswordChange": "पासवर्ड बदलने की अनुमति दें",
|
||||
"Accounts_AllowRealNameChange": "नाम बदलने की अनुमति दें",
|
||||
"Accounts_AllowUserAvatarChange": "उपयोगकर्ता अवतार परिवर्तन की अनुमति दें",
|
||||
"Accounts_AllowUsernameChange": "उपयोगकर्ता नाम बदलने की अनुमति दें",
|
||||
"Accounts_AllowUserProfileChange": "उपयोगकर्ता प्रोफ़ाइल बदलने की अनुमति दें",
|
||||
"Accounts_AvatarBlockUnauthenticatedAccess": "अपुष्ट एक्सेस को अवतारों से ब्लॉक करें",
|
||||
"Accounts_AvatarCacheTime": "अवतार कैश समय",
|
||||
"Accounts_AvatarCacheTime_description": "HTTP प्रोटोकॉल को अवतार छवियों को कैश करने के लिए सेकंड की संख्या बताई गई है।",
|
||||
"Accounts_AvatarExternalProviderUrl": "अवतार बाहरी प्रदाता URL",
|
||||
"Accounts_AvatarExternalProviderUrl_Description": "उदाहरण: `https://acme.com/api/v1/ {उपयोगकर्ता नाम}`",
|
||||
"Accounts_AvatarResize": "अवतार का आकार बदलें",
|
||||
"Accounts_AvatarSize": "अवतार का आकार",
|
||||
"Accounts_BlockedDomainsList": "अवरुद्ध डोमेन सूची",
|
||||
"Accounts_BlockedDomainsList_Description": "अवरुद्ध डोमेन की कोमा से अलग सूची",
|
||||
"Accounts_BlockedUsernameList": "अवरुद्ध उपयोगकर्ता नाम सूची",
|
||||
"Accounts_BlockedUsernameList_Description": "कॉमा-अवरुद्ध उपयोगकर्ता नाम की अलग-अलग सूची (केस-असंवेदनशील)",
|
||||
"Accounts_CustomFields_Description": "एक वैध JSON होना चाहिए जहां कुंजियाँ फ़ील्ड नाम हैं जिसमें फ़ील्ड सेटिंग्स का शब्दकोश है। उदाहरण: \\n`{\\\"role\\\":{ \\\"type\\\": \\\"select\\\", \\\"defaultValue\\\": \\\"student\\\", \\\"options\\\": [\\\"teacher\\\", \\\"student\\\"], \\\"required\\\": true, \\\"modifyRecordField\\\": { \\\"array\\\": true, \\\"field\\\": \\\"roles\\\" } }, \\\"twitter\\\": { \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"minLength\\\": 2, \\\"maxLength\\\": 10 }}`",
|
||||
"Accounts_CustomFieldsToShowInUserInfo": "कस्टम फ़ील्ड उपयोगकर्ता जानकारी में दिखाने के लिए",
|
||||
"Accounts_Default_User_Preferences": "डिफ़ॉल्ट उपयोगकर्ता प्राथमिकताएं",
|
||||
"Accounts_Default_User_Preferences_audioNotifications": "ऑडियो सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"Accounts_Default_User_Preferences_desktopNotifications": "डेस्कटॉप सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"Accounts_Default_User_Preferences_not_available": "उपयोगकर्ता प्राथमिकताएँ प्राप्त करने में विफल, क्योंकि वे उपयोगकर्ता द्वारा अभी तक सेट नहीं किए गए हैं",
|
||||
"Accounts_Default_User_Preferences_pushNotifications": "मोबाइल सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"Accounts_DefaultUsernamePrefixSuggestion": "डिफ़ॉल्ट उपयोगकर्ता नाम उपसर्ग सुझाव",
|
||||
"Accounts_denyUnverifiedEmail": "अयोग्य ईमेल अस्वीकार करें",
|
||||
"Accounts_Email_Activated": "[name]<br/><br/><p>आपका खाता सक्रिय हो गया था।</p>",
|
||||
"Accounts_Email_Activated_Subject": "खाता सक्रिय किया गया",
|
||||
"Accounts_Email_Approved": "[name]<br/><br/><p>आपका खाता स्वीकृत हो गया।</p>",
|
||||
"Accounts_Email_Approved_Subject": "खाता स्वीकृत हुआ",
|
||||
"Accounts_Email_Deactivated": "[name] <br/> <br/> <p> आपका खाता निष्क्रिय कर दिया गया।</p>",
|
||||
"Accounts_Email_Deactivated_Subject": "खाता निष्क्रिय किया गया",
|
||||
"Accounts_EmailVerification": "ई - मेल सत्यापन",
|
||||
"Accounts_EmailVerification_Description": "सुनिश्चित करें कि आपके पास इस सुविधा का उपयोग करने के लिए SMTP सेटिंग्स सही हैं",
|
||||
"Accounts_Enrollment_Email": "नामांकन ईमेल",
|
||||
"Accounts_Enrollment_Email_Default": "<h1>Welcome to <strong>[Site_Name]</strong></h1><p>Go to <a href=\"[Site_URL]\">[Site_URL]</a> and try the best open source chat solution available today!</p>",
|
||||
"Accounts_Enrollment_Email_Description": "आप निम्नलिखित प्लेसहोल्डर का उपयोग कर सकते हैं: \n - `[name]`, `[fname]`, `[lname]` उपयोगकर्ता के पूर्ण नाम के लिए, क्रमशः पहला नाम या अंतिम नाम। \n - । ईमेल] उपयोगकर्ता के ईमेल के लिए। \n - `[Site_Name]` और `[Site_URL]` क्रमशः अनुप्रयोग नाम और URL के लिए। ",
|
||||
"Accounts_Enrollment_Email_Subject_Default": "[Site_Name] में आपका स्वागत है",
|
||||
"Accounts_ForgetUserSessionOnWindowClose": "विंडो बंद होने पर उपयोगकर्ता सत्र को भूल जाएं",
|
||||
"Accounts_Iframe_api_method": "API विधि",
|
||||
"Accounts_Iframe_api_url": "API URL",
|
||||
"Accounts_iframe_enabled": "सक्रिय",
|
||||
"Accounts_iframe_url": "Iframe URL",
|
||||
"Accounts_LoginExpiration": "दिन में प्रवेश की समाप्ति",
|
||||
"Accounts_ManuallyApproveNewUsers": "नए उपयोगकर्ताओं को मैन्युअल रूप से अनुमोदित करें",
|
||||
"Accounts_OAuth_Custom_Authorize_Path": "पथ अधिकृत करें",
|
||||
"Accounts_OAuth_Custom_Button_Color": "बटन का रंग",
|
||||
"Accounts_OAuth_Custom_Button_Label_Color": "बटन टेक्स्ट का रंग",
|
||||
"Accounts_OAuth_Custom_Button_Label_Text": "बटन टेक्स्ट",
|
||||
"Accounts_OAuth_Custom_Enable": "सक्षम करें",
|
||||
"Accounts_OAuth_Custom_id": "Id",
|
||||
"Accounts_OAuth_Custom_Identity_Path": "पहचान पथ",
|
||||
"Accounts_OAuth_Custom_Identity_Token_Sent_Via": "के जरिए पहचान टोकन भेजा गया",
|
||||
"Accounts_OAuth_Custom_Login_Style": "लॉगिन शैली",
|
||||
"Accounts_OAuth_Custom_Merge_Users": "उपयोगकर्ताओं को मर्ज करें",
|
||||
"Accounts_OAuth_Custom_Scope": "क्षेत्र",
|
||||
"Accounts_OAuth_Custom_Secret": "गुप्त",
|
||||
"Accounts_OAuth_Custom_Token_Path": "टोकन पथ",
|
||||
"Accounts_OAuth_Custom_Token_Sent_Via": "के जरिए टोकन भेजा गया",
|
||||
"Accounts_OAuth_Custom_Username_Field": "उपयोगकर्ता नाम फ़ील्ड",
|
||||
"Accounts_OAuth_Drupal": "Drupal लॉगिन सक्षम है",
|
||||
"Accounts_OAuth_Drupal_callback_url": "Drupal oAuth2 रीडायरेक्ट URI",
|
||||
"Accounts_OAuth_Drupal_id": "Drupal oAuth2 क्लाइंट आईडी",
|
||||
"Accounts_OAuth_Drupal_secret": "Drupal oAuth2 क्लाइंट सीक्रेट",
|
||||
"Accounts_OAuth_Facebook": "फेसबुक लोगिन",
|
||||
"Accounts_OAuth_Facebook_callback_url": "फेसबुक कॉलबैक URL",
|
||||
"Accounts_OAuth_Facebook_id": "फेसबुक ऐप आईडी",
|
||||
"Accounts_OAuth_Facebook_secret": "फेसबुक सीक्रेट",
|
||||
"Accounts_OAuth_Github": "OAuth सक्षम",
|
||||
"Accounts_OAuth_Github_callback_url": "GitHub कॉलबैक यूआरएल",
|
||||
"Accounts_OAuth_GitHub_Enterprise": "OAuth सक्षम",
|
||||
"Accounts_OAuth_GitHub_Enterprise_callback_url": "GitHub एंटरप्राइज़ कॉलबैक URL",
|
||||
"Accounts_OAuth_GitHub_Enterprise_id": "क्लाइंट ID",
|
||||
"Accounts_OAuth_GitHub_Enterprise_secret": "क्लाइंट Secret",
|
||||
"Accounts_OAuth_Github_id": "क्लाइंट Id",
|
||||
"Accounts_OAuth_Github_secret": "क्लाइंट Secret",
|
||||
"Accounts_OAuth_Gitlab": "OAuth सक्षम",
|
||||
"Accounts_OAuth_Gitlab_callback_url": "GitLab कॉलबैक URL",
|
||||
"Accounts_OAuth_Gitlab_id": "Gitlab Id",
|
||||
"Accounts_OAuth_Gitlab_identity_path": "पहचान पथ",
|
||||
"Accounts_OAuth_Gitlab_secret": "क्लाइंट Secret",
|
||||
"Accounts_OAuth_Google": "Google लॉगिन",
|
||||
"Accounts_OAuth_Google_callback_url": "Google कॉलबैक URL",
|
||||
"Accounts_OAuth_Google_id": "Google Id",
|
||||
"Accounts_OAuth_Google_secret": "Google Secret",
|
||||
"Accounts_OAuth_Linkedin": "LinkedIn लॉगिन",
|
||||
"Accounts_OAuth_Linkedin_callback_url": "LinkedIn कॉलबैक URL",
|
||||
"Accounts_OAuth_Linkedin_id": "LinkedIn Id",
|
||||
"Accounts_OAuth_Linkedin_secret": "LinkedIn Secret",
|
||||
"Accounts_OAuth_Meteor": "Meteor Login",
|
||||
"Accounts_OAuth_Meteor_callback_url": "Meteor कॉलबैक URL",
|
||||
"Accounts_OAuth_Meteor_id": "Meteor Id",
|
||||
"Accounts_OAuth_Meteor_secret": "Meteor Secret",
|
||||
"Accounts_OAuth_Nextcloud": "OAuth सक्षम",
|
||||
"Accounts_OAuth_Nextcloud_secret": "क्लाइंट Secret",
|
||||
"Accounts_OAuth_Proxy_host": "प्रॉक्सी होस्ट",
|
||||
"Accounts_OAuth_Proxy_services": "प्रॉक्सी सेवाएँ",
|
||||
"Accounts_OAuth_Twitter": "ट्विटर लॉगइन",
|
||||
"Accounts_OAuth_Twitter_callback_url": "ट्विटर कॉलबैक URL",
|
||||
"Accounts_OAuth_Twitter_id": "ट्विटर Id",
|
||||
"Accounts_OAuth_Twitter_secret": "ट्विटर Secret",
|
||||
"Accounts_OAuth_Wordpress": "वर्डप्रेस लॉगिन",
|
||||
"Accounts_OAuth_Wordpress_authorize_path": "पथ अधिकृत करें",
|
||||
"Accounts_OAuth_Wordpress_callback_url": "वर्डप्रेस कॉलबैक URL",
|
||||
"Accounts_OAuth_Wordpress_id": "वर्डप्रेस Id",
|
||||
"Accounts_OAuth_Wordpress_identity_path": "पहचान पथ",
|
||||
"Accounts_OAuth_Wordpress_identity_token_sent_via": "पहचान टोकन भेजा गया",
|
||||
"Accounts_OAuth_Wordpress_scope": "क्षेत्र",
|
||||
"Accounts_OAuth_Wordpress_secret": "वर्डप्रेस Secret",
|
||||
"Accounts_OAuth_Wordpress_server_type_custom": "कस्टम",
|
||||
"Accounts_OAuth_Wordpress_server_type_wordpress_com": "Wordpress.com",
|
||||
"Accounts_OAuth_Wordpress_server_type_wp_oauth_server": "WP OAuth Server Plugin",
|
||||
"Accounts_OAuth_Wordpress_token_path": "टोकन पथ",
|
||||
"Accounts_Password_Policy_AtLeastOneLowercase": "कम से कम एक लोअरकेस में",
|
||||
"Accounts_Password_Policy_AtLeastOneLowercase_Description": "लागू करें कि पासवर्ड में कम से कम एक लोअरकेस वर्ण हो।",
|
||||
"Accounts_Password_Policy_AtLeastOneNumber": "कम से कम एक नंबर",
|
||||
"Accounts_Password_Policy_AtLeastOneNumber_Description": "लागू करें कि एक पासवर्ड में कम से कम एक संख्यात्मक चरित्र होता है।",
|
||||
"Accounts_Password_Policy_AtLeastOneUppercase_Description": "लागू करें कि पासवर्ड में कम से कम एक लोअरकेस वर्ण हो।",
|
||||
"Accounts_Registration_InviteUrlType_Direct": "सीधा",
|
||||
"Accounts_RegistrationForm": "पंजीकरण पत्र",
|
||||
"Accounts_RegistrationForm_Disabled": "उपयोग करने की अनुमति नहीं है",
|
||||
"Accounts_RegistrationForm_Public": "जनता",
|
||||
"Accounts_TwoFactorAuthentication_MaxDelta": "soochna",
|
||||
"Additional_Feedback": "अतिरिक्त प्रतिक्रिया",
|
||||
"App_status_auto_enabled": "सक्रिय",
|
||||
"App_status_disabled": "उपयोग करने की अनुमति नहीं है",
|
||||
"App_status_manually_enabled": "सक्रिय",
|
||||
"Appearance": "दिखावट",
|
||||
"Audio_Notifications_Default_Alert": "ऑडियो सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"Cancel": "रद्द करना",
|
||||
"Cancel_message_input": "रद्द करना",
|
||||
"CAS_enabled": "सक्रिय",
|
||||
"Client_Secret": "क्लाइंट Secret",
|
||||
"Custom": "कस्टम",
|
||||
"Desktop_Notifications_Default_Alert": "डेस्कटॉप सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"Disabled": "उपयोग करने की अनुमति नहीं है",
|
||||
"Edit_Status": "स्थिति संपादित करें",
|
||||
"Email_Notification_Mode_Disabled": "उपयोग करने की अनुमति नहीं है",
|
||||
"Enable": "सक्षम करें",
|
||||
"Enabled": "सक्रिय",
|
||||
"Federation_Matrix_enabled": "सक्रिय",
|
||||
"Filters": "फिल्टर",
|
||||
"Home": "होम",
|
||||
"How_friendly_was_the_chat_agent": "चैट एजेंट कितना दोस्ताना था?",
|
||||
"How_knowledgeable_was_the_chat_agent": "चैट एजेंट कितना जानकार था?",
|
||||
"How_responsive_was_the_chat_agent": "चैट एजेंट कितना उत्तरदायी था?",
|
||||
"How_satisfied_were_you_with_this_chat": "आप इस चैट से कितने संतुष्ट थे?",
|
||||
"Installation": "स्थापना",
|
||||
"LDAP_Authentication": "सक्षम करें",
|
||||
"LDAP_Enable": "सक्षम करें",
|
||||
"LDAP_User_Search_Scope": "क्षेत्र",
|
||||
"Livechat_registration_form": "पंजीकरण ",
|
||||
"Mobile_Push_Notifications_Default_Alert": "मोबाइल सूचनाएं डिफ़ॉल्ट चेतावनी",
|
||||
"New_messages": "नए संदेश",
|
||||
"No": "नहीं",
|
||||
"Options": "विकल्प",
|
||||
"Outlook_Calendar_Enabled": "सक्रिय",
|
||||
"Please_answer_survey": "कृपया इस चैट के बारे में त्वरित सर्वेक्षण का उत्तर देने के लिए एक क्षण लें",
|
||||
"Please_fill_name_and_email": "कृपया नाम और ईमेल भरें",
|
||||
"Public": "जनता",
|
||||
"Push_enable": "सक्षम करें",
|
||||
"RetentionPolicy_Enabled": "सक्रिय",
|
||||
"Scope": "क्षेत्र",
|
||||
"Select_a_department": "एक विभाग का चयन करें",
|
||||
"Select_department": "एक विभाग का चयन करें",
|
||||
"Send": "भेजना",
|
||||
"Skip": "छोड़ें",
|
||||
"Start_Chat": "बातचीत शुरू ",
|
||||
"Survey": "सर्वेक्षण",
|
||||
"Survey_instructions": "प्रत्येक प्रश्न को अपनी संतुष्टि के अनुसार रेट करें, 1 मतलब कि आप पूरी तरह से असंतुष्ट हैं और 5 का अर्थ है कि आप पूरी तरह से संतुष्ट हैं।",
|
||||
"Thank_you_for_your_feedback": "आपकी प्रतिक्रिया के लिए आपका धन्यवाद",
|
||||
"Type_your_email": "अपना ईमेल टाइप करें",
|
||||
"Type_your_message": "अपना संदेश टाइप करें",
|
||||
"Type_your_name": "अपना नाम लिखें",
|
||||
"Upload_file_question": "दस्तावेज अपलोड करें?",
|
||||
"User_left": "उपयोगकर्ता छोड़ दिया",
|
||||
"We_are_offline_Sorry_for_the_inconvenience": "हम ऑफ़लाइन हैं। असुविधा के लिए खेद है।",
|
||||
"Yes": "हाँ",
|
||||
"You": "आप"
|
||||
}
|
||||
22
packages/i18n/src/locales/pa-IN.i18n.json
Normal file
22
packages/i18n/src/locales/pa-IN.i18n.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"500": "ਅੰਦਰੂਨੀ ਸਰਵਰ ਗੜਬੜ",
|
||||
"__count__empty_rooms_will_be_removed_automatically__rooms__": "<bold>{{count}}</bold> empty rooms will be removed automatically:<br/> {{rooms}}.",
|
||||
"Away": "ਦੂਰ",
|
||||
"away": "ਦੂਰ",
|
||||
"Busy": "ਰੁਝੇਵਾਂ",
|
||||
"busy": "ਰੁਝੇਵਾਂ",
|
||||
"Chats_removed": "ਗੱਲਾਂਬਾਤਾਂ ਹਟਾਈਆਂ ਗਈਆਂ",
|
||||
"FEDERATION_Status": "ਹਾਲਤ",
|
||||
"Incoming_Livechats": "ਕਤਾਰਬੱਧ ਗੱਲਾਂਬਾਤਾਂ",
|
||||
"Last_Status": "ਪਿਛਲੀ ਹਾਲਤ",
|
||||
"Learn_more_about_current_chats": "ਮੌਜੂਦਾ ਗੱਲਾਂਬਾਤਾਂ ਬਾਰੇ ਹੋਰ ਜਾਣੋ",
|
||||
"Livechat_forward_open_chats": "ਖੁੱਲ੍ਹੀਆਂ ਗੱਲਾਂਬਾਤਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ",
|
||||
"Offline": "ਗੈਰ-ਹਾਜ਼ਰ",
|
||||
"offline": "ਗੈਰ-ਹਾਜ਼ਰ",
|
||||
"Online": "ਔਨਲਾਈਨ",
|
||||
"online": "ਹਾਜ਼ਰ",
|
||||
"Past_Chats": "ਪਿਛਲੀਆਂ ਗੱਲਾਂਬਾਤਾਂ",
|
||||
"Private_Chats": "ਨਿੱਜੀ ਗੱਲਾਂਬਾਤਾਂ",
|
||||
"set__username__as__role_": "set {{username}} ਨੂੰ {{role}}",
|
||||
"Stats_Online_Users": "ਹਾਜ਼ਰ ਵਰਤੋਂਕਾਰ"
|
||||
}
|
||||
1
packages/i18n/src/locales/se.i18n.json
Normal file
1
packages/i18n/src/locales/se.i18n.json
Normal file
@@ -0,0 +1 @@
|
||||
{ }
|
||||
14
packages/i18n/src/locales/si.i18n.json
Normal file
14
packages/i18n/src/locales/si.i18n.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"500": "අභ්යන්තර සේවාදායකයේ දෝෂයකි",
|
||||
"@username": "@පරිශීලකනාමය",
|
||||
"#channel": "#නාලිකාව",
|
||||
"0_Errors_Only": "0 - දෝෂ පමණි",
|
||||
"1_Errors_and_Information": "1 - දෝෂ සහ තොරතුරු",
|
||||
"12_Hour": "හෝරා 12 ඔරලෝසුව",
|
||||
"24_Hour": "හෝරා 24 ඔරලෝසුව",
|
||||
"Accept": "පිළිගන්න",
|
||||
"Accounts": "ගිණුම්",
|
||||
"You_have_a_new_message": "ඔබට නව පණිවිඩයක් ඇත",
|
||||
"Your_question": "ඔබගේ ප්රශ්නය",
|
||||
"Your_server_link": "ඔබගේ සේවාදායක සබැඳිය"
|
||||
}
|
||||
1
packages/i18n/src/locales/tl.i18n.json
Normal file
1
packages/i18n/src/locales/tl.i18n.json
Normal file
@@ -0,0 +1 @@
|
||||
{ }
|
||||
1217
packages/i18n/src/locales/ug.i18n.json
Normal file
1217
packages/i18n/src/locales/ug.i18n.json
Normal file
File diff suppressed because it is too large
Load Diff
12
packages/i18n/src/resources.ts
Normal file
12
packages/i18n/src/resources.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// dummy
|
||||
export type RocketchatI18nKeys =
|
||||
| 'core.key1'
|
||||
| 'core.key2'
|
||||
| 'onboarding.key1'
|
||||
| 'onboarding.key2'
|
||||
| 'registration.key1'
|
||||
| 'registration.key2'
|
||||
| 'cloud.key1'
|
||||
| 'cloud.key2'
|
||||
| 'subscription.key1'
|
||||
| 'subscription.key2';
|
||||
102
packages/i18n/src/scripts/build.mts
Normal file
102
packages/i18n/src/scripts/build.mts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { argv, exit } from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { distDirectory, languageFromBasename, resourceBasename, resourcesDirectory } from './common.mts';
|
||||
import { normalizeI18nInterpolations } from './normalize.mts';
|
||||
|
||||
async function build() {
|
||||
// read all files in the src/locales directory
|
||||
const resourceFiles = await readdir(resourcesDirectory);
|
||||
const resources = await Promise.all(
|
||||
resourceFiles.map(async (file) => ({
|
||||
language: languageFromBasename(file),
|
||||
content: JSON.parse(await readFile(join(resourcesDirectory, file), 'utf8')),
|
||||
})),
|
||||
);
|
||||
|
||||
// normalize the interpolations and collect the stats
|
||||
const countsByNormalization: Record<string, number> = {
|
||||
__: 0,
|
||||
sprintf: 0,
|
||||
i18nextComponentsArray: 0,
|
||||
nullValues: 0,
|
||||
nestedPlurals: 0,
|
||||
};
|
||||
|
||||
// Uncomment these lines for verbose output
|
||||
// const languageLength = resources.reduce((max, resource) => Math.max(max, resource.language.length), 0);
|
||||
// const statKeyLength = Object.keys(countsByNormalization).reduce((max, key) => Math.max(max, key.length), 0);
|
||||
for (const resource of resources) {
|
||||
resource.content = normalizeI18nInterpolations(resource.content, resource.language, (statName: string) => {
|
||||
countsByNormalization[statName]++;
|
||||
// console.log(`${statName.padEnd(statKeyLength)} ${resource.language.padStart(languageLength)} ${JSON.stringify(record.key)}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(`Number of keys using __ and replaced by native i18next interpolation('{{' '}}') key: ${countsByNormalization.__} keys`);
|
||||
console.log(`Number of keys with (explicit) null values: ${countsByNormalization.nullValues} keys`);
|
||||
console.log(`Number of keys using sprintf: ${countsByNormalization.sprintf} keys`);
|
||||
console.log(`Number of keys using i18next components array (<number></number>): ${countsByNormalization.i18nextComponentsArray} keys`);
|
||||
console.log(`Number of keys with nested plurals: ${countsByNormalization.nestedPlurals} keys`);
|
||||
|
||||
await mkdir(distDirectory, { recursive: true });
|
||||
|
||||
// write the files
|
||||
|
||||
// ./resources/*.i18n.json
|
||||
await mkdir(join(distDirectory, 'resources'), { recursive: true });
|
||||
for (const resource of resources) {
|
||||
await writeFile(join(distDirectory, 'resources', resourceBasename(resource.language)), JSON.stringify(resource.content, null, 2));
|
||||
}
|
||||
|
||||
// ./resources
|
||||
const baseResource = resources.find((r) => r.language === 'en');
|
||||
if (!baseResource) {
|
||||
throw new Error('Base resource "en" not found');
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
join(distDirectory, 'resources.d.ts'),
|
||||
`export interface RocketchatI18n {
|
||||
${Object.keys(baseResource.content)
|
||||
.map((key) => `${JSON.stringify(key)}: string;`)
|
||||
.join('\n\t')}
|
||||
}
|
||||
export type RocketchatI18nKeys = keyof RocketchatI18n;
|
||||
declare const resources: Record<string, RocketchatI18n>;
|
||||
export default resources;`,
|
||||
);
|
||||
|
||||
// ./languages
|
||||
const languages = resources.map(({ language }) => language);
|
||||
const languagesSerialized = JSON.stringify(languages, null, 2);
|
||||
|
||||
await writeFile(join(distDirectory, 'languages.js'), `export default ${languagesSerialized};`);
|
||||
|
||||
await writeFile(
|
||||
join(distDirectory, 'languages.cjs'),
|
||||
`"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = ${languagesSerialized};`,
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
join(distDirectory, 'languages.d.ts'),
|
||||
`declare const languages: string[];
|
||||
export default languages;`,
|
||||
);
|
||||
}
|
||||
|
||||
if (import.meta.url.startsWith('file:')) {
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
|
||||
if (argv[1] === modulePath) {
|
||||
build().catch((error) => {
|
||||
console.error(error);
|
||||
exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
646
packages/i18n/src/scripts/check.mts
Normal file
646
packages/i18n/src/scripts/check.mts
Normal file
@@ -0,0 +1,646 @@
|
||||
import { argv, exit, stderr, stdout } from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { formatWithOptions, parseArgs, styleText } from 'node:util';
|
||||
|
||||
import { baseLanguage, getLanguagePlurals, getResourceLanguages, readContent, readResource, writeResource } from './common.mts';
|
||||
|
||||
type TaskOptions = {
|
||||
fix?: boolean;
|
||||
};
|
||||
|
||||
let errorCount = 0;
|
||||
|
||||
const describeTask =
|
||||
(
|
||||
task: string,
|
||||
fn: () => AsyncGenerator<{
|
||||
lint: (reportError: (format?: any, ...param: any[]) => void) => Promise<void>;
|
||||
fix?: (throwError: (format?: any, ...param: any[]) => void) => Promise<void>;
|
||||
}>,
|
||||
) =>
|
||||
async (options: TaskOptions) => {
|
||||
const stdoutSupportsColor = styleText('blue', `.`, { stream: stdout }) !== '.';
|
||||
const stderrSupportsColor = styleText('blue', `.`, { stream: stderr }) !== '.';
|
||||
|
||||
const throwError = (format?: any, ...param: any[]) => {
|
||||
throw new Error(formatWithOptions({ colors: stdoutSupportsColor }, format, ...param));
|
||||
};
|
||||
|
||||
const reportError = (format?: any, ...param: any[]) => {
|
||||
console.error(
|
||||
styleText('red', '✘', { stream: stderr }),
|
||||
styleText('gray', `${task}:`, { stream: stderr }),
|
||||
formatWithOptions({ colors: stderrSupportsColor }, format, ...param),
|
||||
);
|
||||
errorCount++;
|
||||
};
|
||||
|
||||
for await (const result of fn()) {
|
||||
if (!result) continue;
|
||||
|
||||
if (options.fix) {
|
||||
try {
|
||||
if (!result.fix) {
|
||||
await result.lint(throwError);
|
||||
continue;
|
||||
}
|
||||
|
||||
await result.fix(throwError);
|
||||
|
||||
console.log(styleText('blue', '✔', { stream: stdout }), styleText('gray', `${task}:`, { stream: stdout }), 'fixes applied');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
styleText('red', '✘', { stream: stdout }),
|
||||
styleText('gray', `${task}:`, { stream: stdout }),
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
console.error(styleText('gray', ` cannot apply fixes automatically, run without --fix to see all errors`, { stream: stdout }));
|
||||
errorCount++;
|
||||
}
|
||||
} else {
|
||||
await result.lint(reportError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JavaScript objects hoist integer-like keys to the front, so a resource file cannot hold one anywhere else;
|
||||
* the expected order has to match what `JSON.parse` yields, otherwise the sorted file never lints clean.
|
||||
* Only canonical array indices in `[0, 2 ** 32 - 2]` are hoisted; `4294967295` and above stay in place.
|
||||
*/
|
||||
const isIntegerLikeKey = (key: string) => {
|
||||
const asNumber = Number(key);
|
||||
return Number.isInteger(asNumber) && asNumber >= 0 && asNumber <= 2 ** 32 - 2 && String(asNumber) === key;
|
||||
};
|
||||
|
||||
const compareBaseKeys = (a: string, b: string) => {
|
||||
const aIsIntegerLike = isIntegerLikeKey(a);
|
||||
const bIsIntegerLike = isIntegerLikeKey(b);
|
||||
|
||||
if (aIsIntegerLike !== bIsIntegerLike) return aIsIntegerLike ? -1 : 1;
|
||||
if (aIsIntegerLike && bIsIntegerLike) return Number(a) - Number(b);
|
||||
|
||||
// Keys differing only in case tie under the case-insensitive comparison; break it so the order stays canonical
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase(), 'en') || (a < b ? -1 : 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort keys of the base language (en) alphabetically and write back the sorted resource file if necessary
|
||||
*/
|
||||
const sortBaseKeys = describeTask('sort-base-keys', async function* () {
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
|
||||
const keys = Object.keys(baseResource);
|
||||
const sortedKeys = keys.toSorted(compareBaseKeys);
|
||||
|
||||
if (keys.join(',') === sortedKeys.join(',')) return;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const beforeKey = i > 0 ? keys[i - 1] : undefined;
|
||||
const j = sortedKeys.indexOf(key);
|
||||
const expectedBeforeKey = j > 0 ? sortedKeys[j - 1] : undefined;
|
||||
|
||||
if (beforeKey !== expectedBeforeKey) {
|
||||
if (expectedBeforeKey) {
|
||||
reportError('%o should be after %o', keys[i], expectedBeforeKey);
|
||||
} else {
|
||||
reportError('%o should be the first key', keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fix: async () => {
|
||||
const sortedResource: Record<string, unknown> = {};
|
||||
for (const key of sortedKeys) {
|
||||
sortedResource[key] = baseResource[key];
|
||||
}
|
||||
|
||||
await writeResource(baseLanguage, sortedResource);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Apply the order of the base language (en) to all other languages
|
||||
*/
|
||||
const sortKeys = describeTask('sort-keys', async function* () {
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
const baseKeys = new Set(Object.keys(baseResource));
|
||||
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
if (language === baseLanguage) continue;
|
||||
|
||||
const resource = await readResource(language);
|
||||
const resourceKeys = new Set(Object.keys(resource));
|
||||
const extraKeys = resourceKeys.difference(baseKeys);
|
||||
|
||||
const sortedResource: Record<string, unknown> = {};
|
||||
|
||||
for (const key of baseKeys) {
|
||||
if (!resourceKeys.has(key)) continue;
|
||||
sortedResource[key] = resource[key];
|
||||
}
|
||||
|
||||
for (const key of extraKeys) {
|
||||
sortedResource[key] = resource[key];
|
||||
}
|
||||
|
||||
if (Object.keys(resource).join(',') === Object.keys(sortedResource).join(',')) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
const keys = Object.keys(resource);
|
||||
const sortedKeys = Object.keys(sortedResource);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (extraKeys.has(key)) continue;
|
||||
|
||||
const j = sortedKeys.indexOf(key);
|
||||
const expectedBeforeKey = j > 0 ? sortedKeys[j - 1] : undefined;
|
||||
const beforeKey = i > 0 ? keys[i - 1] : undefined;
|
||||
|
||||
if (beforeKey !== expectedBeforeKey) {
|
||||
if (expectedBeforeKey) {
|
||||
reportError('%s: %o should be after %o', language, keys[i], expectedBeforeKey);
|
||||
} else {
|
||||
reportError('%s: %o should be the first key', language, keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fix: async () => {
|
||||
await writeResource(language, sortedResource);
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wipes extra keys from all language files that are not present in the base language (en)
|
||||
*/
|
||||
const wipeExtraKeys = describeTask('wipe-extra-keys', async function* () {
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
const baseKeys = new Set(Object.keys(baseResource));
|
||||
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
if (language === baseLanguage) continue;
|
||||
|
||||
const resource = await readResource(language);
|
||||
const resourceKeys = new Set(Object.keys(resource));
|
||||
|
||||
if (resourceKeys.difference(baseKeys).size === 0) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
const extraKeys = resourceKeys.difference(baseKeys);
|
||||
for (const key of extraKeys) {
|
||||
reportError('%s: has extra key %o', language, key);
|
||||
}
|
||||
},
|
||||
fix: async () => {
|
||||
const wipedResource: Record<string, unknown> = {};
|
||||
// Traversing the own resource keys to preserve the original order
|
||||
for (const key of Object.keys(resource)) {
|
||||
if (!baseKeys.has(key)) continue;
|
||||
wipedResource[key] = resource[key];
|
||||
}
|
||||
|
||||
await writeResource(language, wipedResource);
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wipes invalid plural forms from all language files (only "zero", "one", "two", "few", "many", "other" are valid)
|
||||
*/
|
||||
const wipeInvalidPlurals = describeTask('wipe-invalid-plurals', async function* () {
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
const resource = await readResource(language);
|
||||
const plurals = getLanguagePlurals(language).concat(['zero']); // 'zero' is special in i18next
|
||||
|
||||
for (const [key, translation] of Object.entries(resource)) {
|
||||
if (typeof translation !== 'object' || !translation) continue;
|
||||
|
||||
const translationPlurals = Object.keys(translation);
|
||||
for (const plural of translationPlurals) {
|
||||
if (!plurals.includes(plural)) {
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
reportError('%s: key %o has invalid plural form %o', language, key, plural);
|
||||
},
|
||||
fix: async () => {
|
||||
const fixedResource: Record<string, unknown> = { ...resource };
|
||||
fixedResource[key] = Object.fromEntries(Object.entries(translation).filter(([p]) => plurals.includes(p)));
|
||||
await writeResource(language, fixedResource);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds missing plural forms in all language files
|
||||
*/
|
||||
const findMissingPlurals = describeTask('find-missing-plurals', async function* () {
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
if (language === baseLanguage) continue;
|
||||
|
||||
const resource = await readResource(language);
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
const plurals = getLanguagePlurals(language);
|
||||
|
||||
for (const [key, translation] of Object.entries(baseResource)) {
|
||||
if (typeof translation !== 'object' || !translation) continue;
|
||||
if (!(key in resource)) continue;
|
||||
|
||||
const translationPlurals = Object.keys(translation);
|
||||
const resourceTranslation = resource[key];
|
||||
if (typeof resourceTranslation !== 'object' || !resourceTranslation) continue;
|
||||
|
||||
for (const plural of translationPlurals) {
|
||||
if (!plurals.includes(plural)) continue;
|
||||
if (plural in resourceTranslation) continue;
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
reportError('%s: key %o is missing plural form %o', language, key, plural);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function* listTranslations(resource: Record<string, unknown>) {
|
||||
for (const [key, translation] of Object.entries(resource)) {
|
||||
if (typeof translation === 'string') {
|
||||
yield { key, translation } as const;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof translation === 'object' && translation) {
|
||||
for (const [plural, pluralTranslation] of Object.entries(translation)) {
|
||||
if (typeof pluralTranslation !== 'string') continue;
|
||||
yield { key, plural, translation: pluralTranslation } as const;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const replaceDoubleUnderscorePlaceholders = describeTask('replace-2-underscores', async function* () {
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
const placeholderRegex = /__(.*?)__/g;
|
||||
const identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
|
||||
for (const language of languages) {
|
||||
const resource = await readResource(language);
|
||||
|
||||
for (const { key, plural, translation } of listTranslations(resource)) {
|
||||
const matches = Array.from(translation.matchAll(placeholderRegex));
|
||||
if (!matches.length) continue;
|
||||
|
||||
for (const match of matches) {
|
||||
if (!identifierRegex.test(match[1])) {
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
if (plural) {
|
||||
reportError('%s: key %o (plural %o) has invalid placeholder %o', language, key, plural, match[0]);
|
||||
} else {
|
||||
reportError('%s: key %o has invalid placeholder %o', language, key, match[0]);
|
||||
}
|
||||
},
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
if (plural) {
|
||||
reportError('%s: key %o (plural %o) has placeholder %o, should be %o', language, key, plural, match[0], `{{${match[1]}}}`);
|
||||
} else {
|
||||
reportError('%s: key %o has placeholder %o, should be %o', language, key, match[0], `{{${match[1]}}}`);
|
||||
}
|
||||
},
|
||||
fix: async () => {
|
||||
const fixedResource = { ...resource };
|
||||
if (plural) {
|
||||
fixedResource[key] = {
|
||||
...(fixedResource[key] as Record<string, string>),
|
||||
[plural]: translation.replace(placeholderRegex, `{{${match[1]}}}`),
|
||||
};
|
||||
} else {
|
||||
fixedResource[key] = translation.replace(placeholderRegex, `{{${match[1]}}}`);
|
||||
}
|
||||
await writeResource(language, fixedResource);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const trimEndOfFile = describeTask('trim-eof', async function* () {
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
const content = await readContent(language);
|
||||
const trimmedContent = content.replace(/\s+$/g, '');
|
||||
|
||||
if (trimmedContent.length === content.length) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
reportError('%s: has trailing whitespace at end of file', language);
|
||||
},
|
||||
fix: async () => {
|
||||
await writeResource(language, JSON.parse(trimmedContent));
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const extractPlaceholders = (translation: string): Set<string> => {
|
||||
const placeholders = new Set<string>();
|
||||
const placeholderRegex = /{{(.+?)(,.*?)?}}/g;
|
||||
let match;
|
||||
while ((match = placeholderRegex.exec(translation)) !== null) {
|
||||
placeholders.add(match[1]);
|
||||
}
|
||||
return placeholders;
|
||||
};
|
||||
|
||||
const encodedKey = (key: string, plural?: string) => (plural ? `${key}|${plural}` : key);
|
||||
|
||||
/**
|
||||
* Finds translations that are missing placeholders present in the base language (en)
|
||||
*/
|
||||
const missingPlaceholders = describeTask('missing-placeholders', async function* () {
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
const baseTranslations = listTranslations(baseResource);
|
||||
const basePlaceholdersByEncodedKey = new Map<string, Set<string>>();
|
||||
|
||||
for (const { key: baseKey, plural: basePlural, translation: baseTranslation } of baseTranslations) {
|
||||
basePlaceholdersByEncodedKey.set(encodedKey(baseKey, basePlural), extractPlaceholders(baseTranslation));
|
||||
}
|
||||
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
if (language === baseLanguage) continue;
|
||||
|
||||
const resource = await readResource(language);
|
||||
const translations = listTranslations(resource);
|
||||
|
||||
for (const { key, plural, translation } of translations) {
|
||||
const basePlaceholders = basePlaceholdersByEncodedKey.get(encodedKey(key, plural));
|
||||
if (!basePlaceholders) continue;
|
||||
|
||||
const placeholders = extractPlaceholders(translation);
|
||||
|
||||
for (const basePlaceholder of basePlaceholders) {
|
||||
if (placeholders.has(basePlaceholder)) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
if (plural) {
|
||||
reportError('%s: key %o (plural %o) is missing placeholder %o', language, key, plural, basePlaceholder);
|
||||
return;
|
||||
}
|
||||
reportError('%s: key %o is missing placeholder %o', language, key, basePlaceholder);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds translations that have extra placeholders not present in the base language (en)
|
||||
*/
|
||||
const extraPlaceholders = describeTask('extra-placeholders', async function* () {
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
const baseTranslations = listTranslations(baseResource);
|
||||
const basePlaceholdersByEncodedKey = new Map<string, Set<string>>();
|
||||
|
||||
for (const { key: baseKey, plural: basePlural, translation: baseTranslation } of baseTranslations) {
|
||||
basePlaceholdersByEncodedKey.set(encodedKey(baseKey, basePlural), extractPlaceholders(baseTranslation));
|
||||
}
|
||||
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
if (language === baseLanguage) continue;
|
||||
|
||||
const resource = await readResource(language);
|
||||
const translations = listTranslations(resource);
|
||||
|
||||
for (const { key, plural, translation } of translations) {
|
||||
const basePlaceholders = basePlaceholdersByEncodedKey.get(encodedKey(key, plural));
|
||||
if (!basePlaceholders) continue;
|
||||
|
||||
const placeholders = extractPlaceholders(translation);
|
||||
|
||||
for (const placeholder of placeholders) {
|
||||
if (basePlaceholders.has(placeholder)) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
reportError('%s: key %o%s has extra placeholder %o', language, key, plural ? ` (plural ${plural})` : '', placeholder);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const findPositionalParams = describeTask('find-sprintf-params', async function* () {
|
||||
const resource = await readResource(baseLanguage);
|
||||
|
||||
for (const { key, plural, translation } of listTranslations(resource)) {
|
||||
if (!translation.includes('%s')) continue;
|
||||
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
if (plural) {
|
||||
reportError('key %o (plural %o) has positional parameter %o, should be named parameter like %o', key, plural, '%s', '{{param}}');
|
||||
} else {
|
||||
reportError('key %o has positional parameter %o, should be named parameter like %o', key, '%s', '{{param}}');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function* detectDuplicateJsonKeys(text: string) {
|
||||
let pos = 0;
|
||||
|
||||
const skip = () => {
|
||||
while (pos < text.length && ' \t\r\n'.includes(text[pos])) pos++;
|
||||
};
|
||||
|
||||
const readString = (): string => {
|
||||
pos++;
|
||||
let s = '';
|
||||
while (pos < text.length && text[pos] !== '"') {
|
||||
if (text[pos] === '\\') {
|
||||
s += text[pos++];
|
||||
}
|
||||
s += text[pos++];
|
||||
}
|
||||
pos++;
|
||||
return s;
|
||||
};
|
||||
|
||||
function* skipValue(): Generator<{ key: string; parentKey?: string }> {
|
||||
skip();
|
||||
if (text[pos] === '"') readString();
|
||||
else if (text[pos] === '{') yield* readObject();
|
||||
else if (text[pos] === '[') yield* readArray();
|
||||
else while (pos < text.length && !',}]'.includes(text[pos]) && !' \t\r\n'.includes(text[pos])) pos++;
|
||||
}
|
||||
|
||||
function* readObject(parentKey?: string) {
|
||||
pos++;
|
||||
skip();
|
||||
const seen = new Set<string>();
|
||||
while (pos < text.length && text[pos] !== '}') {
|
||||
skip();
|
||||
const key = readString();
|
||||
skip();
|
||||
pos++;
|
||||
if (seen.has(key)) {
|
||||
yield { key, parentKey };
|
||||
}
|
||||
seen.add(key);
|
||||
yield* skipValue();
|
||||
skip();
|
||||
if (text[pos] === ',') pos++;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
function* readArray() {
|
||||
pos++;
|
||||
skip();
|
||||
while (pos < text.length && text[pos] !== ']') {
|
||||
yield* skipValue();
|
||||
skip();
|
||||
if (text[pos] === ',') pos++;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
skip();
|
||||
if (pos < text.length && text[pos] === '{') yield* readObject();
|
||||
}
|
||||
|
||||
const findDuplicateKeys = describeTask('find-duplicate-keys', async function* () {
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
for (const language of languages) {
|
||||
const content = await readContent(language);
|
||||
|
||||
for (const { key, parentKey } of detectDuplicateJsonKeys(content)) {
|
||||
yield {
|
||||
lint: async (reportError) => {
|
||||
if (parentKey) {
|
||||
reportError('%s: duplicate key %o in %o', language, key, parentKey);
|
||||
} else {
|
||||
reportError('%s: duplicate key %o', language, key);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Map of all available tasks
|
||||
*/
|
||||
const tasksByName = {
|
||||
'sort-base-keys': sortBaseKeys,
|
||||
'sort-keys': sortKeys,
|
||||
'wipe-extra-keys': wipeExtraKeys,
|
||||
'wipe-invalid-plurals': wipeInvalidPlurals,
|
||||
'find-missing-plurals': findMissingPlurals,
|
||||
'replace-2-underscores': replaceDoubleUnderscorePlaceholders,
|
||||
'trim-eof': trimEndOfFile,
|
||||
'find-sprintf-params': findPositionalParams,
|
||||
'missing-placeholders': missingPlaceholders,
|
||||
'extra-placeholders': extraPlaceholders,
|
||||
'find-duplicate-keys': findDuplicateKeys,
|
||||
} as const;
|
||||
|
||||
async function check({ fix, task }: { fix?: boolean; task?: string[] } = {}) {
|
||||
// We're lenient by default excluding some non-critical tasks
|
||||
const tasks = new Set<keyof typeof tasksByName>([
|
||||
// 'sort-base-keys' has to run before 'sort-keys', which derives every other locale's order from it
|
||||
'sort-base-keys',
|
||||
'sort-keys',
|
||||
'wipe-extra-keys',
|
||||
'wipe-invalid-plurals',
|
||||
'find-missing-plurals',
|
||||
'replace-2-underscores',
|
||||
'trim-eof',
|
||||
'missing-placeholders',
|
||||
'extra-placeholders',
|
||||
'find-duplicate-keys',
|
||||
]);
|
||||
|
||||
if (task?.length) {
|
||||
tasks.clear();
|
||||
task.filter((taskName): taskName is keyof typeof tasksByName => taskName in tasksByName).forEach((taskName) => tasks.add(taskName));
|
||||
}
|
||||
|
||||
if (tasks.size === 0) {
|
||||
throw new Error('No valid tasks selected.');
|
||||
}
|
||||
|
||||
for (const taskName of tasks) {
|
||||
const task = tasksByName[taskName];
|
||||
await task({ fix });
|
||||
}
|
||||
|
||||
if (errorCount > 0) {
|
||||
throw new Error(`${errorCount} error(s) found.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url.startsWith('file:')) {
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
|
||||
if (argv[1] === modulePath) {
|
||||
const { values } = parseArgs({
|
||||
args: argv.slice(2),
|
||||
options: {
|
||||
fix: { type: 'boolean', short: 'f' },
|
||||
task: {
|
||||
type: 'string',
|
||||
multiple: true,
|
||||
short: 't',
|
||||
choices: Object.keys(tasksByName),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
check(values).catch((error) => {
|
||||
console.error(error);
|
||||
exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
47
packages/i18n/src/scripts/common.mts
Normal file
47
packages/i18n/src/scripts/common.mts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { basename, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import i18next from 'i18next';
|
||||
|
||||
export const baseLanguage = 'en';
|
||||
|
||||
export async function getResourceLanguages() {
|
||||
const resourceFiles = await readdir(resourcesDirectory);
|
||||
return resourceFiles.map((file) => languageFromBasename(file));
|
||||
}
|
||||
|
||||
export const getLanguagePlurals = (language: string): string[] => {
|
||||
// @ts-expect-error - faulty module resolution from ESM package
|
||||
if (!i18next.isInitialized) {
|
||||
i18next.init({ initImmediate: false });
|
||||
}
|
||||
|
||||
// @ts-expect-error - faulty module resolution from ESM package
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
||||
return i18next.services.pluralResolver.getSuffixes(language).map((suffix: string) => suffix.slice(1));
|
||||
};
|
||||
|
||||
export async function readContent(language: string) {
|
||||
return readFile(join(resourcesDirectory, resourceBasename(language)), 'utf8');
|
||||
}
|
||||
|
||||
export async function readResource(language: string): Promise<Record<string, unknown>> {
|
||||
const result = JSON.parse(await readContent(language));
|
||||
if (typeof result !== 'object' || result === null || Array.isArray(result)) {
|
||||
throw new Error(`Invalid resource format for language "${language}"`);
|
||||
}
|
||||
return result as Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function writeResource(language: string, resource: unknown) {
|
||||
const content = JSON.stringify(resource, null, 2);
|
||||
return writeFile(join(resourcesDirectory, resourceBasename(language)), content, 'utf8');
|
||||
}
|
||||
|
||||
export const rootDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
export const resourcesDirectory = join(rootDirectory, 'src', 'locales');
|
||||
export const distDirectory = join(rootDirectory, 'dist');
|
||||
|
||||
export const resourceBasename = (language: string) => `${language}.i18n.json`;
|
||||
export const languageFromBasename = (path: string) => basename(path, '.i18n.json');
|
||||
107
packages/i18n/src/scripts/normalize.mts
Normal file
107
packages/i18n/src/scripts/normalize.mts
Normal file
@@ -0,0 +1,107 @@
|
||||
const replacements = {
|
||||
__: {
|
||||
regex: /__(.*?)__/g,
|
||||
replacement: '{{$1}}',
|
||||
},
|
||||
sprintf: {
|
||||
regex: /%[0-9]*[.]?[0-9]*[a-z]/g,
|
||||
replacement: undefined,
|
||||
},
|
||||
i18nextComponentsArray: {
|
||||
regex: /<(\d+?)>(.+?)<\/\1>/g,
|
||||
replacement: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const replaceI18nInterpolation = (translation: string) => {
|
||||
if (!translation) return [undefined, false] as const;
|
||||
const exist = translation?.match(replacements.__.regex);
|
||||
return [translation?.replace(replacements.__.regex, replacements.__.replacement), Boolean(exist)] as const;
|
||||
};
|
||||
|
||||
const replaceSprintfInterpolation = (translation: string) => {
|
||||
const exist = translation?.match(replacements.sprintf.regex);
|
||||
return [undefined, Boolean(exist)] as const;
|
||||
};
|
||||
|
||||
const replaceI18nextComponentsArrayInterpolation = (translation: string) => {
|
||||
const exist = translation?.match(replacements.i18nextComponentsArray.regex);
|
||||
return [undefined, Boolean(exist)] as const;
|
||||
};
|
||||
|
||||
const replaceNullValuesInterpolation = (translation: string) => {
|
||||
return [undefined, translation === null] as const;
|
||||
};
|
||||
|
||||
const generator =
|
||||
(fn: (translation: string) => readonly [string | undefined, boolean], id: string) =>
|
||||
(dictionary: Record<string, any>, language: string, cb?: (id: string, info: { language: string; key: string }) => void) =>
|
||||
Object.entries(dictionary).reduce((dic, [key, value]) => {
|
||||
const [replacement, exist] = fn(value);
|
||||
if (exist) {
|
||||
cb?.(id, { language, key });
|
||||
}
|
||||
if (replacement) {
|
||||
dic[key] = replacement;
|
||||
}
|
||||
return dic;
|
||||
}, dictionary);
|
||||
|
||||
const replaceI18nInterpolations = generator(replaceI18nInterpolation, '__');
|
||||
|
||||
const replaceSprintfInterpolations = generator(replaceSprintfInterpolation, 'sprintf');
|
||||
|
||||
const replaceI18nextComponentsArrayInterpolations = generator(replaceI18nextComponentsArrayInterpolation, 'i18nextComponentsArray');
|
||||
|
||||
const replaceNullValues = generator(replaceNullValuesInterpolation, 'nullValues');
|
||||
|
||||
const replaceNestedPlurals = (
|
||||
dictionary: Record<string, any>,
|
||||
language: string,
|
||||
cb?: (statName: string, info: { language: string; key: string }) => void,
|
||||
) => {
|
||||
const entries = [];
|
||||
const plurals = ['zero', 'one', 'two', 'few', 'many', 'other'];
|
||||
|
||||
for (const [key, translation] of Object.entries(dictionary).toSorted(([a], [b]) => a.localeCompare(b))) {
|
||||
if (typeof translation === 'string') {
|
||||
entries.push([key, translation]);
|
||||
} else if (typeof translation === 'object' && translation !== null) {
|
||||
const existingKeys = new Set(Object.keys(translation));
|
||||
const exceedingKeys = existingKeys.difference(new Set(plurals));
|
||||
if (exceedingKeys.size > 0) console.error(`Invalid plurals in key "${key}" found: ${Array.from(exceedingKeys).join(', ')}`);
|
||||
|
||||
for (const plural of plurals) {
|
||||
if (!(plural in translation)) continue;
|
||||
|
||||
entries.push([`${key}_${plural}`, translation[plural]]);
|
||||
}
|
||||
cb?.('nestedPlurals', { language, key });
|
||||
}
|
||||
}
|
||||
|
||||
return Object.fromEntries(entries) as Record<string, any>;
|
||||
};
|
||||
|
||||
const pipe =
|
||||
<Y, X extends any[]>(...fns: ((y: Y, ...x: X) => Y)[]) =>
|
||||
(y: Y, ...x: X) =>
|
||||
fns.reduce((v, f) => {
|
||||
return f(v, ...x);
|
||||
}, y);
|
||||
|
||||
export const normalizeI18nInterpolations = (
|
||||
dictionary: Record<string, any>,
|
||||
language: string,
|
||||
cb?: (statName: string, info: { language: string; key: string }) => void,
|
||||
) => {
|
||||
const result = pipe(
|
||||
replaceNestedPlurals,
|
||||
replaceNullValues,
|
||||
replaceI18nInterpolations,
|
||||
replaceI18nextComponentsArrayInterpolations,
|
||||
replaceSprintfInterpolations,
|
||||
)(dictionary, language, cb);
|
||||
|
||||
return result;
|
||||
};
|
||||
23
packages/i18n/src/scripts/normalize.spec.ts
Normal file
23
packages/i18n/src/scripts/normalize.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { normalizeI18nInterpolations } from './normalize.mts';
|
||||
|
||||
describe('normalizeI18nInterpolations', () => {
|
||||
it('should return the same object if there are no interpolations', async () => {
|
||||
const fileContents = { test: 'test' };
|
||||
|
||||
expect(normalizeI18nInterpolations(fileContents, 'en')).toEqual(fileContents);
|
||||
});
|
||||
|
||||
it('should return the replace the interpolation(__) keys to the desired one ({{}})', async () => {
|
||||
const fileContents = {
|
||||
shouldChange: 'this is the __key__',
|
||||
shouldNotChange: 'normal one',
|
||||
multipleKeys: 'sentence __with__ multiple __keys__',
|
||||
};
|
||||
|
||||
expect(normalizeI18nInterpolations(fileContents, 'en')).toEqual({
|
||||
shouldChange: 'this is the {{key}}',
|
||||
shouldNotChange: 'normal one',
|
||||
multipleKeys: 'sentence {{with}} multiple {{keys}}',
|
||||
});
|
||||
});
|
||||
});
|
||||
126
packages/i18n/src/scripts/replace-sprintf.mts
Normal file
126
packages/i18n/src/scripts/replace-sprintf.mts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { argv, exit, stdin as input, stdout as output } from 'node:process';
|
||||
import { createInterface } from 'node:readline/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
import { baseLanguage, getResourceLanguages, readResource, resourcesDirectory, writeResource } from './common.mts';
|
||||
|
||||
/**
|
||||
* Counts occurrences of a substring in a string
|
||||
*/
|
||||
const countOccurrences = (str: string, substring: string): number => {
|
||||
let count = 0;
|
||||
let position = str.indexOf(substring);
|
||||
|
||||
while (position !== -1) {
|
||||
count++;
|
||||
position = str.indexOf(substring, position + 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main function to replace %s tokens with named parameters
|
||||
*/
|
||||
const replaceSprintfParams = async (translationKey: string): Promise<void> => {
|
||||
if (!translationKey) {
|
||||
throw new Error('Please provide a translation key as parameter');
|
||||
}
|
||||
|
||||
const languages = await getResourceLanguages();
|
||||
|
||||
if (languages.length === 0) {
|
||||
throw new Error(`No translation files found in ${resourcesDirectory}`);
|
||||
}
|
||||
|
||||
console.log(`Found ${languages.length} translation files`);
|
||||
|
||||
const baseResource = await readResource(baseLanguage);
|
||||
|
||||
if (!baseResource[translationKey]) {
|
||||
throw new Error(`Translation key "${translationKey}" not found in ${baseLanguage} translations`);
|
||||
}
|
||||
|
||||
if (typeof baseResource[translationKey] !== 'string') {
|
||||
throw new Error(`Translation key "${translationKey}" is not a string in ${baseLanguage} translations`);
|
||||
}
|
||||
|
||||
const baseTranslation = baseResource[translationKey];
|
||||
const tokenCount = countOccurrences(baseTranslation, '%s');
|
||||
|
||||
console.log(`Found translation key "${translationKey}" with value: "${baseTranslation}"`);
|
||||
console.log(`This string contains ${tokenCount} "%s" tokens`);
|
||||
|
||||
if (tokenCount === 0) {
|
||||
console.log('No %s tokens found, nothing to do');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prompt for parameter names
|
||||
const rl = createInterface({ input, output });
|
||||
const promptMessage = `Please provide ${tokenCount} parameter names (comma-separated): `;
|
||||
const paramNamesInput = await rl.question(promptMessage);
|
||||
rl.close();
|
||||
|
||||
// Split and trim parameter names
|
||||
const paramNames = paramNamesInput.split(',').map((name) => name.trim());
|
||||
|
||||
if (paramNames.length !== tokenCount) {
|
||||
throw new Error(`Expected ${tokenCount} parameter names, but got ${paramNames.length}`);
|
||||
}
|
||||
|
||||
// Process all translation files
|
||||
for (const language of languages) {
|
||||
const resource = await readResource(language);
|
||||
|
||||
if (resource[translationKey]) {
|
||||
const translation = resource[translationKey];
|
||||
|
||||
if (typeof translation !== 'string') {
|
||||
console.warn(`Skipping "${translationKey}" in ${language} translations: not a string`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let updatedValue = translation;
|
||||
|
||||
let paramIndex = 0;
|
||||
|
||||
// Replace each %s token with a named parameter
|
||||
while (updatedValue.includes('%s') && paramIndex < paramNames.length) {
|
||||
updatedValue = updatedValue.replace('%s', `{{${paramNames[paramIndex]}}}`);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
resource[translationKey] = updatedValue;
|
||||
|
||||
await writeResource(language, resource);
|
||||
console.log(`Updated "${translationKey}" in ${language} translations to: "${updatedValue}"`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('All translation files have been updated');
|
||||
};
|
||||
|
||||
if (import.meta.url.startsWith('file:')) {
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
|
||||
if (argv[1] === modulePath) {
|
||||
const { positionals } = parseArgs({
|
||||
args: argv.slice(2),
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (positionals.length === 0) {
|
||||
console.error('Please provide at least one translation key as parameter');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (const arg of positionals) {
|
||||
await replaceSprintfParams(arg).catch((error) => {
|
||||
console.error(error);
|
||||
exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
10
packages/i18n/tsconfig.build.json
Normal file
10
packages/i18n/tsconfig.build.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.spec.ts", "./src/scripts/**/*"]
|
||||
}
|
||||
18
packages/i18n/tsconfig.json
Normal file
18
packages/i18n/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@rocket.chat/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2024",
|
||||
"module": "node18",
|
||||
"moduleResolution": "node16",
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"esModuleInterop": true,
|
||||
"rootDirs": ["./src", "./"],
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"allowJs": false,
|
||||
"lib": ["ES2024", "ESNext.Collection"],
|
||||
},
|
||||
"include": ["./src/**/*", "./jest.config.ts"],
|
||||
}
|
||||
Reference in New Issue
Block a user