fix(i18n): system-path JS translations, release POT extraction, locale-aware date picker#629
Conversation
…e-aware date picker - Load script (JS) translations from the WordPress system path (wp-content/languages/plugins/) before the bundled .mo, mirroring load_plugin_textdomain — React strings now honor user-supplied translations the same way PHP strings do (weDevsOfficial#560) - Release makepot (Gruntfile) was excluding views/assets/src, so React strings never reached the POT / translate.wordpress.org. Exclude views/assets/dist + vendor instead — extraction now covers the React UI (weDevsOfficial#595, weDevsOfficial#597, weDevsOfficial#598, weDevsOfficial#600, weDevsOfficial#475) - Translatable "Tools" / "Import Tools" nav + breadcrumb instead of the literal "Importtools" (weDevsOfficial#601) - Wrap remaining shadcn ui primitive strings (pagination, dialog, sheet, color-picker) in __() - Replace every native <input type="date"> with a shadcn react-day-picker DatePicker whose month/weekday/day labels localize via Intl(PM_Vars.locale); Today / Clear / dd-mm-yyyy are translatable - Expose DatePicker + Calendar on window.PM.ui so Pro uses the same instance - Regenerate POT and fill the bn_BD catalog Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughUpdates translation loading and POT generation, and adds a locale-aware date picker stack used across several UI screens. ChangesTranslation loading and extraction
Date picker UI and shared i18n
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
views/assets/src/components/ui/pagination.jsx (1)
83-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMove
aria-hiddenoff the ellipsis wrapper.Line 88’s translated sr-only text is still unreachable because the parent
<span>isaria-hidden. That leaves the control purely decorative to assistive tech, so the new"More pages"label never gets announced.Suggested fix
const PaginationEllipsis = ({ className, ...props }) => ( <span - aria-hidden className={cn("flex h-9 w-9 items-center justify-center", className)} {...props}> - <MoreHorizontal className="h-4 w-4" /> + <MoreHorizontal className="h-4 w-4" aria-hidden="true" /> <span className="sr-only">{__('More pages', 'wedevs-project-manager')}</span> </span> )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@views/assets/src/components/ui/pagination.jsx` around lines 83 - 88, The ellipsis control in the pagination component is hiding its accessible label because the wrapper span in the pagination item is marked aria-hidden. Update the PaginationEllipsis/PaginationItem markup so the decorative MoreHorizontal icon stays hidden if needed, but the sr-only “More pages” text is not inside an aria-hidden container and can be announced by assistive tech.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@views/assets/src/components/ui/calendar.jsx`:
- Around line 34-38: The month dropdown formatter in calendar.jsx is using the
browser default locale instead of the site/plugin locale, so update the
formatMonthDropdown callback inside the formatters object to use
getPmLocaleTag() from the date-locale helper. Import getPmLocaleTag in the
component and pass it to date.toLocaleString so the month labels match the rest
of the calendar and the active WordPress site locale.
In `@views/assets/src/components/ui/date-picker.jsx`:
- Around line 19-25: The parseISO helper currently accepts overflow dates
because new Date(y, m - 1, d) normalizes invalid inputs, so update parseISO to
validate the reconstructed date against the parsed year, month, and day before
returning it. Keep the existing guards in parseISO, then after creating the Date
object, compare its year/month/day components to the original y, m, and d and
return undefined unless they match exactly; this prevents impossible dates from
being accepted by the date-picker component.
- Around line 94-108: The “Today” action in date-picker.jsx bypasses the
existing range checks and can emit an out-of-bounds date. Update the Today
button handler in the date-picker component so it respects the same `min`/`max`
constraints used by the calendar grid before calling `emit`, and only emit today
when it falls within range; otherwise disable or no-op the action. Use the
existing `emit` logic and the component’s `min`/`max` props in the date-picker
UI to keep behavior consistent.
In `@views/assets/src/lib/date-locale.js`:
- Around line 3-18: The locale normalization in getPmLocaleTag only replaces the
first underscore, so Intl formatter setup can still receive invalid tags and
throw during module initialization. Update getPmLocaleTag to normalize full
WordPress slugs by converting all underscores to hyphens, and guard the
formatter initialization around tag, numberFmt, captionFmt, weekdayFmt, and
longDateFmt with a try/catch that falls back to en-US if Intl still rejects the
locale.
---
Outside diff comments:
In `@views/assets/src/components/ui/pagination.jsx`:
- Around line 83-88: The ellipsis control in the pagination component is hiding
its accessible label because the wrapper span in the pagination item is marked
aria-hidden. Update the PaginationEllipsis/PaginationItem markup so the
decorative MoreHorizontal icon stays hidden if needed, but the sr-only “More
pages” text is not inside an aria-hidden container and can be announced by
assistive tech.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 55893c21-13b5-4201-8d0f-f12b92bf4f48
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
Gruntfile.jslanguages/wedevs-project-manager-bn_BD.molanguages/wedevs-project-manager-bn_BD.polibs/functions.phppackage.jsonviews/assets/src/components/layout/TopBar.jsxviews/assets/src/components/my-tasks/MyTasksPage/index.jsxviews/assets/src/components/my-tasks/MyTasksPage/parts/NewTaskSheet.jsxviews/assets/src/components/projects/MilestonesPage/parts/MilestoneForm.jsxviews/assets/src/components/tasks/TaskDetailSheet/index.jsxviews/assets/src/components/tasks/TaskListSection.jsxviews/assets/src/components/ui/calendar.jsxviews/assets/src/components/ui/color-picker.jsxviews/assets/src/components/ui/date-picker.jsxviews/assets/src/components/ui/dialog.jsxviews/assets/src/components/ui/pagination.jsxviews/assets/src/components/ui/sheet.jsxviews/assets/src/index.jsxviews/assets/src/lib/date-locale.js
| formatters={{ | ||
| formatMonthDropdown: (date) => | ||
| date.toLocaleString("default", { month: "short" }), | ||
| ...formatters, | ||
| }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
cat -n views/assets/src/components/ui/calendar.jsxRepository: file_read:views/assets/src/components/ui/calendar.jsx
Length of output: 2143
🏁 Script executed:
grep -r "PM_Vars" views/assets/src --include="*.jsx" --include="*.js" | grep -i "locale\|site_user" | head -20Repository: search_locale_usage
Length of output: 2041
🏁 Script executed:
grep -r "DayPicker" views/assets/src --include="*.jsx" --include="*.js" -A 5 -B 5 | grep -i "locale" | head -20Repository: search_daypicker_locale
Length of output: 2053
🏁 Script executed:
cat -n views/assets/src/components/ui/calendar.jsxRepository: weDevsOfficial/wp-project-manager
Length of output: 8399
🏁 Script executed:
grep -r "PM_Vars" views/assets/src --include="*.jsx" --include="*.js" | head -20Repository: weDevsOfficial/wp-project-manager
Length of output: 3667
🏁 Script executed:
grep -r "locale" views/assets/src --include="*.jsx" --include="*.js" | head -20Repository: weDevsOfficial/wp-project-manager
Length of output: 1884
🏁 Script executed:
cat -n views/assets/src/lib/date-locale.jsRepository: read_date_locale
Length of output: 2032
🏁 Script executed:
cat -n views/assets/src/lib/date-locale.jsRepository: weDevsOfficial/wp-project-manager
Length of output: 1764
Use the plugin locale for month dropdown labels.
formatMonthDropdown uses date.toLocaleString("default", ...) which relies on the browser's locale, causing month names in the dropdown to differ from the rest of the calendar if the user's browser setting differs from the WordPress site locale.
The codebase already provides views/assets/src/lib/date-locale.js with getPmLocaleTag() to retrieve the site's locale (e.g., bn-BD). Import this helper and pass the correct locale tag to toLocaleString:
import { getPmLocaleTag } from "`@/lib/date-locale`"
// Inside formatters
formatMonthDropdown: (date) =>
date.toLocaleString(getPmLocaleTag(), { month: "short" }),This ensures consistency with the getCaption formatter used elsewhere and aligns the dropdown with the active site configuration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@views/assets/src/components/ui/calendar.jsx` around lines 34 - 38, The month
dropdown formatter in calendar.jsx is using the browser default locale instead
of the site/plugin locale, so update the formatMonthDropdown callback inside the
formatters object to use getPmLocaleTag() from the date-locale helper. Import
getPmLocaleTag in the component and pass it to date.toLocaleString so the month
labels match the rest of the calendar and the active WordPress site locale.
| function parseISO(value) { | ||
| if (!value || typeof value !== 'string') return undefined | ||
| const [y, m, d] = value.split('-').map(Number) | ||
| if (!y || !m || !d) return undefined | ||
| const date = new Date(y, m - 1, d) | ||
| return isNaN(date.getTime()) ? undefined : date | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
python3 - <<'PY'
import datetime
# Simulate JS Date behavior for overflow
# JS: new Date(2026, 1, 31) -> 2026-03-03 (Feb has 28 days in 2026)
# Python: datetime(2026, 2, 31) throws error. We need to simulate JS overflow.
def js_date(y, m, d):
"""Simulate JS new Date(y, m, d) behavior where m is 0-indexed."""
# In JS, months are 0-indexed. m-1 in the code converts string month to JS index.
# So new Date(2026, 1, 31) is Feb 31.
# JS automatically rolls over: Feb 31 = Mar 3 (non-leap year)
try:
# We can't easily simulate JS overflow in Python without a library,
# but we know the behavior: new Date(2026, 1, 31) -> 2026-03-03
# Let's just prove the logic of the fix using Python's safe date
# The fix logic is: if the date we get back doesn't match what we put in, it's invalid.
# Test case 1: Valid date
test_y, test_m, test_d = 2026, 1, 31 # Feb 31 (invalid)
# JS would interpret this as March 3rd.
# So: JS_New_Date(2026, 1, 31) -> Mar 3, 2026
# Fetching components:
# JS_Get_Year() -> 2026
# JS_Get_Month() -> 2 (March, 0-indexed)
# JS_Get_Date() -> 3
# Check: 2026==2026 (ok), 2==1 (FAIL), 3==31 (FAIL). -> Returns undefined. Correct.
# Test case 2: Valid date
# JS_New_Date(2026, 0, 31) -> Jan 31
# Components: 2026, 0, 31
# Check: 2026==2026, 0==0, 31==31. -> Returns date. Correct.
print("Logic verification:")
print("Case 1 (Invalid Feb 31): Input (2026, 1, 31). JS rolls to Mar 3 (2, 3).")
print(" Check: year 2026==2026, month 2!=1, date 3!=31. Fix returns undefined. CORRECT.")
print("Case 2 (Valid Jan 31): Input (2026, 0, 31).")
print(" Check: year 2026==2026, month 0==0, date 31==31. Fix returns date. CORRECT.")
print("Conclusion: The proposed fix logic is sound for catching JS Date normalization.")
except Exception as e:
print(f"Error: {e}")
PYRepository: weDevsOfficial/wp-project-manager
Length of output: 171
Reject overflow dates in parseISO.
JavaScript's new Date(y, m - 1, d) silently normalizes impossible dates. For instance, 2026-02-31 becomes 2026-03-03, causing the UI to display a different date than the stored value and allowing invalid data to persist.
Replace the current validation with a component check to ensure the reconstructed date matches the input exactly:
Suggested fix
function parseISO(value) {
if (!value || typeof value !== 'string') return undefined
const [y, m, d] = value.split('-').map(Number)
if (!y || !m || !d) return undefined
const date = new Date(y, m - 1, d)
- return isNaN(date.getTime()) ? undefined : date
+ if (isNaN(date.getTime())) return undefined
+ if (
+ date.getFullYear() !== y ||
+ date.getMonth() !== m - 1 ||
+ date.getDate() !== d
+ ) {
+ return undefined
+ }
+ return date
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function parseISO(value) { | |
| if (!value || typeof value !== 'string') return undefined | |
| const [y, m, d] = value.split('-').map(Number) | |
| if (!y || !m || !d) return undefined | |
| const date = new Date(y, m - 1, d) | |
| return isNaN(date.getTime()) ? undefined : date | |
| } | |
| function parseISO(value) { | |
| if (!value || typeof value !== 'string') return undefined | |
| const [y, m, d] = value.split('-').map(Number) | |
| if (!y || !m || !d) return undefined | |
| const date = new Date(y, m - 1, d) | |
| if (isNaN(date.getTime())) return undefined | |
| if ( | |
| date.getFullYear() !== y || | |
| date.getMonth() !== m - 1 || | |
| date.getDate() !== d | |
| ) { | |
| return undefined | |
| } | |
| return date | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@views/assets/src/components/ui/date-picker.jsx` around lines 19 - 25, The
parseISO helper currently accepts overflow dates because new Date(y, m - 1, d)
normalizes invalid inputs, so update parseISO to validate the reconstructed date
against the parsed year, month, and day before returning it. Keep the existing
guards in parseISO, then after creating the Date object, compare its
year/month/day components to the original y, m, and d and return undefined
unless they match exactly; this prevents impossible dates from being accepted by
the date-picker component.
| <div className="flex items-center justify-between border-t border-pm-border px-3 py-2"> | ||
| <button | ||
| type="button" | ||
| className="text-sm font-medium text-pm-accent hover:underline" | ||
| onClick={() => emit(undefined)} | ||
| > | ||
| {__('Clear', 'wedevs-project-manager')} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="text-sm font-medium text-pm-accent hover:underline" | ||
| onClick={() => emit(new Date())} | ||
| > | ||
| {__('Today', 'wedevs-project-manager')} | ||
| </button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Honor min/max when the user clicks “Today”.
The grid disables out-of-range days, but Line 105 bypasses that and emits new Date() unconditionally. Any caller using min/max to enforce valid ranges can still receive an invalid value through this shortcut.
Suggested fix
const minDate = parseISO(min)
const maxDate = parseISO(max)
+ const today = parseISO(toISO(new Date()))
+ const todayDisabled =
+ (today && minDate && today < minDate) ||
+ (today && maxDate && today > maxDate)
const disabledMatcher =
minDate || maxDate ? { ...(minDate && { before: minDate }), ...(maxDate && { after: maxDate }) } : undefined
...
<button
type="button"
+ disabled={todayDisabled}
className="text-sm font-medium text-pm-accent hover:underline"
- onClick={() => emit(new Date())}
+ onClick={() => emit(today)}
>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@views/assets/src/components/ui/date-picker.jsx` around lines 94 - 108, The
“Today” action in date-picker.jsx bypasses the existing range checks and can
emit an out-of-bounds date. Update the Today button handler in the date-picker
component so it respects the same `min`/`max` constraints used by the calendar
grid before calling `emit`, and only emit today when it falls within range;
otherwise disable or no-op the action. Use the existing `emit` logic and the
component’s `min`/`max` props in the date-picker UI to keep behavior consistent.
| export function getPmLocaleTag() { | ||
| const raw = | ||
| typeof PM_Vars !== 'undefined' && PM_Vars.locale ? PM_Vars.locale : 'en_US' | ||
| return raw.replace('_', '-') | ||
| } | ||
|
|
||
| const tag = getPmLocaleTag() | ||
|
|
||
| const numberFmt = new Intl.NumberFormat(tag) | ||
| const captionFmt = new Intl.DateTimeFormat(tag, { month: 'long', year: 'numeric' }) | ||
| const weekdayFmt = new Intl.DateTimeFormat(tag, { weekday: 'short' }) | ||
| const longDateFmt = new Intl.DateTimeFormat(tag, { | ||
| day: '2-digit', | ||
| month: '2-digit', | ||
| year: 'numeric', | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
Normalize full WordPress locale slugs before creating Intl formatters.
The current raw.replace('_', '-') only swaps the first separator, causing Intl constructors to throw RangeError for locales with multiple underscores (e.g., de_DE_formal becomes invalid de-DE_formal). This breaks module import and crashes the application on screens using this helper.
Use replaceAll('_', '-') to convert all separators to BCP 47 format, and wrap the initialization in a try...catch to safely fallback to en-US if an invalid tag persists.
Suggested fix
export function getPmLocaleTag() {
const raw =
typeof PM_Vars !== 'undefined' && PM_Vars.locale ? PM_Vars.locale : 'en_US'
- return raw.replace('_', '-')
+ const normalized = raw.replaceAll('_', '-')
+
+ try {
+ new Intl.DateTimeFormat(normalized)
+ return normalized
+ } catch {
+ return 'en-US'
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function getPmLocaleTag() { | |
| const raw = | |
| typeof PM_Vars !== 'undefined' && PM_Vars.locale ? PM_Vars.locale : 'en_US' | |
| return raw.replace('_', '-') | |
| } | |
| const tag = getPmLocaleTag() | |
| const numberFmt = new Intl.NumberFormat(tag) | |
| const captionFmt = new Intl.DateTimeFormat(tag, { month: 'long', year: 'numeric' }) | |
| const weekdayFmt = new Intl.DateTimeFormat(tag, { weekday: 'short' }) | |
| const longDateFmt = new Intl.DateTimeFormat(tag, { | |
| day: '2-digit', | |
| month: '2-digit', | |
| year: 'numeric', | |
| }) | |
| export function getPmLocaleTag() { | |
| const raw = | |
| typeof PM_Vars !== 'undefined' && PM_Vars.locale ? PM_Vars.locale : 'en_US' | |
| const normalized = raw.replaceAll('_', '-') | |
| try { | |
| new Intl.DateTimeFormat(normalized) | |
| return normalized | |
| } catch { | |
| return 'en-US' | |
| } | |
| } | |
| const tag = getPmLocaleTag() | |
| const numberFmt = new Intl.NumberFormat(tag) | |
| const captionFmt = new Intl.DateTimeFormat(tag, { month: 'long', year: 'numeric' }) | |
| const weekdayFmt = new Intl.DateTimeFormat(tag, { weekday: 'short' }) | |
| const longDateFmt = new Intl.DateTimeFormat(tag, { | |
| day: '2-digit', | |
| month: '2-digit', | |
| year: 'numeric', | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@views/assets/src/lib/date-locale.js` around lines 3 - 18, The locale
normalization in getPmLocaleTag only replaces the first underscore, so Intl
formatter setup can still receive invalid tags and throw during module
initialization. Update getPmLocaleTag to normalize full WordPress slugs by
converting all underscores to hyphens, and guard the formatter initialization
around tag, numberFmt, captionFmt, weekdayFmt, and longDateFmt with a try/catch
that falls back to en-US if Intl still rejects the locale.
Summary
Fixes the long-standing i18n issues reported against the plugin and standardizes date pickers on a single translatable shadcn calendar.
Root cause: React strings never reached the POT
The release
makepottask (Gruntfile.js) excludedviews/assets/src, so no React string was ever extracted towedevs-project-manager.pot/ translate.wordpress.org. Many strings were already wrapped in__()but had nothing to translate against. Now excludesviews/assets/dist+vendorinstead, matchingpackage.json'smakepot.Changes
wedevs_pm_get_translations_for_plugin_domain()now readswp-content/languages/plugins/{domain}-{locale}.mobefore the bundled.mo, mirroringload_plugin_textdomain. Locale resolved viadetermine_locale()+plugin_locale. (Loading .mo files for this plugin #560)Importtools. (The navigation displays Importtools and not a translatable string #601)ui/primitive strings (pagination, dialog, sheet, color-picker).<input type="date">(14 across 5 files) with a shadcnreact-day-pickerDatePicker. Month / weekday / day labels localize viaIntl(PM_Vars.locale); Today / Clear / dd-mm-yyyy are translatable. Works for any locale, no extra locale bundles.DatePicker+Calendaronwindow.PM.uiso the Pro plugin shares the same instance.bn_BDcatalog.Issues
Fixes #560, #601
Refs #595, #597, #598, #600, #475, #470, #468
Testing
pnpm buildclean;pnpm makepotnow extracts React strings (verifiedCurrent Tasks,Templates, report descriptions present).bn_BD: nav, breadcrumb, reports, and the calendar render in Bangla.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation