Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Prettify the l10n issue #4928

Merged
merged 10 commits into from
Jun 1, 2022
11 changes: 9 additions & 2 deletions packages/x-date-pickers/src/locales/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ import { getPickersLocalization } from './utils/getPickersLocalization';

// This object is not Partial<PickersLocaleText> because it is the default values
const enUSPickers: PickersLocaleText = {
// Calendar navigation
previousMonth: 'Previous month',
nextMonth: 'Next month',

// View navigation
openPreviousView: 'open previous view',
openNextView: 'open next view',

// DateRange placeholders
start: 'Start',
end: 'End',

// Action bar
cancelButtonLabel: 'Cancel',
clearButtonLabel: 'Clear',
okButtonLabel: 'OK',
todayButtonLabel: 'Today',
start: 'Start',
end: 'End',
};

export const DEFAULT_LOCALE = enUSPickers;
Expand Down
11 changes: 9 additions & 2 deletions packages/x-date-pickers/src/locales/frFR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import { PickersLocaleText } from './utils/pickersLocaleTextApi';
import { getPickersLocalization } from './utils/getPickersLocalization';

const frFRPickers: Partial<PickersLocaleText> = {
// Calendar navigation
previousMonth: 'Mois prรฉcรฉdent',
nextMonth: 'Mois suivant',

// View navigation
openPreviousView: 'Ouvrir la vue prรฉcรฉdente',
openNextView: 'Ouvrir la vue suivante',

// DateRange placeholders
start: 'Dรฉbut',
end: 'Fin',

// Action bar
cancelButtonLabel: 'Annuler',
clearButtonLabel: 'Vider',
okButtonLabel: 'OK',
todayButtonLabel: "Aujourd'hui",
start: 'Dรฉbut',
end: 'Fin',
};

export const frFR = getPickersLocalization(frFRPickers);
197 changes: 134 additions & 63 deletions scripts/l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ const GIT_REPO = 'mui-x';
const L10N_ISSUE_ID = 3211;
const SOURCE_CODE_REPO = `https://github.com/${GIT_ORGANIZATION}/${GIT_REPO}`;

const packagesWithL10n = [
{
key: 'data-grid',
reportName: '๐Ÿง‘โ€๐Ÿ’ผ DataGrid, DataGridPro, DataGridPremium',
constantsRelativePath: 'packages/grid/x-data-grid/src/constants/localeTextConstants.ts',
localesRelativePath: 'packages/grid/x-data-grid/src/locales',
},
{
key: 'pickers',
reportName: '๐Ÿ•’ Date and Time Pickers',
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
constantsRelativePath: 'packages/x-date-pickers/src/locales/enUS.ts',
localesRelativePath: 'packages/x-date-pickers/src/locales',
},
];

const BABEL_PLUGINS = [require.resolve('@babel/plugin-syntax-typescript')];

type Translations = Record<string, babelTypes.Node>;
Expand Down Expand Up @@ -46,8 +61,8 @@ function plugin(existingTranslations: Translations): babel.PluginObj {
return;
}

// Test if the variable name follows the pattern xxXXGrid
if (!/[a-z]{2}[A-Z]{2}Grid/.test(node.id.name)) {
// Test if the variable name follows the pattern xxXXGrid or xxXXPickers
if (!/[a-z]{2}[A-Z]{2}(Grid|Pickers)/.test(node.id.name)) {
visitorPath.skip();
return;
}
Expand Down Expand Up @@ -133,7 +148,7 @@ function extractTranslations(translationsPath: string): [TranslationsByGroup, Tr
return [translationsByGroup, translations];
}

function findLocales(localesDirectory: string) {
function findLocales(localesDirectory: string, constantsPath: string) {
const items = fse.readdirSync(localesDirectory);
const locales: any[] = [];
const localeRegex = /^[a-z]{2}[A-Z]{2}/;
Expand All @@ -146,7 +161,10 @@ function findLocales(localesDirectory: string) {

const localePath = path.resolve(localesDirectory, item);
const code = match[0];
locales.push([localePath, code]);
if (constantsPath !== localePath) {
// Ignore the locale used as a reference
locales.push([localePath, code]);
}
});

return locales;
Expand Down Expand Up @@ -201,19 +219,65 @@ function countryToFlag(isoCode: string) {
: isoCode;
}

async function generateReport(
missingTranslations: Record<string, { path: string; locations: number[] }>,
) {
interface MissingKey {
currentLineContent: string;
lineIndex: number;
}
interface MissingTranslations {
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
[localeCode: string]: {
[packageCode: string]: {
path: string;
missingKeys: MissingKey[];
};
};
}

async function generateReport(missingTranslations: MissingTranslations) {
const lastCommitRef = await git('log -n 1 --pretty="format:%H"');
const lines: string[] = [];
Object.entries(missingTranslations).forEach(([code, info]) => {
if (info.locations.length === 0) {
return;
}
lines.push(`### ${countryToFlag(code.slice(2))} ${code.slice(0, 2)}-${code.slice(2)}`);
info.locations.forEach((location) => {
const permalink = `${SOURCE_CODE_REPO}/blob/${lastCommitRef}/${info.path}#L${location}`;
lines.push(permalink);
Object.entries(missingTranslations).forEach(([languageCode, infoPerPackage]) => {
lines.push('');
lines.push(
`### ${countryToFlag(languageCode.slice(2))} ${languageCode.slice(0, 2)}-${languageCode.slice(
2,
)}`,
);

packagesWithL10n.forEach(({ key: packageKey, reportName, localesRelativePath }) => {
const info = infoPerPackage[packageKey];

lines.push('<details>');

const fileName = `${languageCode.slice(0, 2).toLowerCase()}${languageCode
.slice(2)
.toUpperCase()}.ts`;
const filePath = `${localesRelativePath}/${fileName}`;
if (!info) {
lines.push(` <summary>${reportName}: file to create </summary>`);
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
lines.push('');
lines.push(` > Add file \`${filePath}\` to start contribution to this locale`);
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
} else if (info.missingKeys.length === 0) {
lines.push(` <summary>${reportName} (Done โœ…)</summary>`);
lines.push('');
lines.push(` > This locale has been completed by the community ๐Ÿš€`);
lines.push(
` > You can still look for typo fix or improvements in [the translation file](${SOURCE_CODE_REPO}/blob/${lastCommitRef}/${filePath}) ๐Ÿ•ต`,
);
} else {
lines.push(` <summary>${reportName}(${info.missingKeys.length} remaining)</summary>`);
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
lines.push('');
info.missingKeys.forEach((missingKey) => {
const permalink = `${SOURCE_CODE_REPO}/blob/${lastCommitRef}/${info.path}#L${missingKey.lineIndex}`;
let lineContent = missingKey.currentLineContent;

if (lineContent[lineContent.length - 1] === ',') {
lineContent = lineContent.slice(0, lineContent.length - 1);
}
lines.push(` - [\`${lineContent}\`](${permalink})`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to escape ``. The line content may contain backstick when string interpolation is used, e.g. groupColumn. Adding a space before closing seems to work. https://meta.stackexchange.com/a/138916

Suggested change
lines.push(` - [\`${lineContent}\`](${permalink})`);
lines.push(` - [\``${lineContent}\`](${permalink}) ``); // Add space before `` to escape `

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
}

lines.push('</details>');
});
});
return lines.join('\n');
Expand All @@ -229,7 +293,6 @@ async function updateIssue(githubToken, newMessage) {

Run \`yarn l10n --report\` to update the list below โฌ‡๏ธ

## DataGrid / DataGridPro
${newMessage}
`;
await octokit
Expand All @@ -256,64 +319,72 @@ async function run(argv: yargs.ArgumentsCamelCase<HandlerArgv>) {
const { report, githubToken } = argv;
const workspaceRoot = path.resolve(__dirname, '../');

const constantsPath = path.join(
workspaceRoot,
'packages/grid/x-data-grid/src/constants/localeTextConstants.ts',
);
const [baseTranslationsByGroup, baseTranslations] = extractTranslations(constantsPath);
const missingTranslations: Record<string, any> = {};

const localesDirectory = path.resolve(workspaceRoot, 'packages/grid/x-data-grid/src/locales');
const locales = findLocales(localesDirectory);
packagesWithL10n.forEach((packageInfo) => {
const constantsPath = path.join(workspaceRoot, packageInfo.constantsRelativePath);
const [baseTranslationsByGroup, baseTranslations] = extractTranslations(constantsPath);

const missingTranslations: Record<string, any> = {};
const localesDirectory = path.resolve(workspaceRoot, packageInfo.localesRelativePath);
const locales = findLocales(localesDirectory, constantsPath);

locales.forEach(([localePath, localeCode]) => {
const {
translations: existingTranslations,
transformedCode,
rawCode,
} = extractAndReplaceTranslations(localePath);
locales.forEach(([localePath, localeCode]) => {
const {
translations: existingTranslations,
transformedCode,
rawCode,
} = extractAndReplaceTranslations(localePath);

if (!transformedCode || Object.keys(existingTranslations).length === 0) {
return;
}
if (!transformedCode || Object.keys(existingTranslations).length === 0) {
return;
}

const codeWithNewTranslations = injectTranslations(
transformedCode,
existingTranslations,
baseTranslationsByGroup,
);
const codeWithNewTranslations = injectTranslations(
transformedCode,
existingTranslations,
baseTranslationsByGroup,
);

const prettierConfigPath = path.join(workspaceRoot, 'prettier.config.js');
const prettierConfig = prettier.resolveConfig.sync(localePath, { config: prettierConfigPath });
const prettierConfigPath = path.join(workspaceRoot, 'prettier.config.js');
const prettierConfig = prettier.resolveConfig.sync(localePath, {
config: prettierConfigPath,
});

const prettifiedCode = prettier.format(codeWithNewTranslations, {
...prettierConfig,
filepath: localePath,
});
const prettifiedCode = prettier.format(codeWithNewTranslations, {
...prettierConfig,
filepath: localePath,
});

const lines = rawCode.split('\n');
Object.entries(baseTranslations).forEach(([key]) => {
if (!existingTranslations[key]) {
if (!missingTranslations[localeCode]) {
missingTranslations[localeCode] = {
path: localePath.replace(workspaceRoot, '').slice(1), // Remove leading slash
locations: [],
};
}
const location = lines.findIndex((line) => line.trim().startsWith(`// ${key}:`));
// Ignore when both the translation and the placeholder are missing
if (location >= 0) {
missingTranslations[localeCode].locations.push(location + 1);
// We always set the `locations` to [] such that we can differentiate translation completed from un-existing translations
if (!missingTranslations[localeCode]) {
missingTranslations[localeCode] = {};
}
if (!missingTranslations[localeCode][packageInfo.key]) {
missingTranslations[localeCode][packageInfo.key] = {
path: localePath.replace(workspaceRoot, '').slice(1), // Remove leading slash
missingKeys: [],
};
}
const lines = rawCode.split('\n');
Object.entries(baseTranslations).forEach(([key]) => {
if (!existingTranslations[key]) {
const location = lines.findIndex((line) => line.trim().startsWith(`// ${key}:`));
// Ignore when both the translation and the placeholder are missing
if (location >= 0) {
missingTranslations[localeCode][packageInfo.key].missingKeys.push({
currentLineContent: lines[location].trim().slice(3),
lineIndex: location + 1,
});
}
}
});

if (!report) {
fse.writeFileSync(localePath, prettifiedCode);
// eslint-disable-next-line no-console
console.log(`Wrote ${localeCode} locale.`);
}
});

if (!report) {
fse.writeFileSync(localePath, prettifiedCode);
// eslint-disable-next-line no-console
console.log(`Wrote ${localeCode} locale.`);
}
});

if (report) {
Expand Down