OBPIH-7488 support localized date patterns#5485
Conversation
| default.dashboard.label=Tablero | ||
| default.data.label=datos | ||
| default.date.format=dd / MMM / aaaa hh\: mm\: ss | ||
| default.date.format=dd/MMM/yyyy hh:mm:ss a z |
There was a problem hiding this comment.
while I was testing I noticed that the spanish message for the default date was looking weird. It actually wouldn't parse properly for me. "a" is "AM"/"PM" so I think this format was just wrong...
grails-app/i18n/messages.properties
Outdated
| date.format.date.label=dd/MMM/yyyy | ||
| date.format.dateTime.label=dd/MMM/yyyy HH:mm:ss | ||
| date.format.dateTimeZone.label=dd/MMM/yyyy HH:mm:ss XXX | ||
| date.format.time.label=MM/yyyy |
There was a problem hiding this comment.
These are the localizations for the new date formats. We should only need to override them for locales that want a different format.
But lets discuss what patterns we want to go with. These will display like:
01/Jan/2025
01/Jan/2025 00:00:00
01/Jan/2025 00:00:00 +01:00
00:00:00
Which I picked since that's what we already use throughout most of the app.
| .withDisplayFormat(attrs.displayStyle || attrs.format ? null : DateDisplayFormat.GSP) | ||
| .build() | ||
|
|
||
| return dateFormatterManager.format(attrs.date, context) |
There was a problem hiding this comment.
This allows us to specify additional attrs when using <g:formatDate>. We shouldn't need to use these extra attrs very often but it gives us the flexibility to override behaviours as needed for any edge cases that we have
|
|
||
| // Use Grails' namespace because we're overriding the default behaviour and so that the tag lib | ||
| // is accessible automatically throughout the app. | ||
| static namespace = 'g' |
There was a problem hiding this comment.
I might revert this whole class. I wanted to standardize how we localize messages in general but it wasn't working with special characters while I was testing locally. TBD after testing my branch on a deployed environment.
| DATE('date.format.date.label', DateTimeFormatter.ofPattern('dd/MMM/yyyy')), | ||
| DATE_TIME('date.format.dateTime.label', DateTimeFormatter.ofPattern('dd/MMM/yyyy HH:mm:ss')), | ||
| DATE_TIME_ZONE('date.format.dateTimeZone.label', DateTimeFormatter.ofPattern('dd/MMM/yyyy HH:mm:ss XXX')), | ||
| TIME('date.format.time.label', DateTimeFormatter.ofPattern('HH:mm:ss')) |
There was a problem hiding this comment.
The enum version of the dates in message.properties. We don't strictly need this class but I find it much nicer to be able to work with enums instead of hard coded strings. That way if we need to change the underlying labels we can do so without refactoring all the usages
| * Context object containing the configuration fields for formatting dates. | ||
| * For a majority of cases the default settings can be used and so this context object will not be required. | ||
| */ | ||
| class DateFormatterContext { |
There was a problem hiding this comment.
I just moved this class to its own file. The only addition is displayStyleOverride
| * A simple wrapper class on a translatable message/label. | ||
| * Used to indicate that translations can be applied to the message. | ||
| */ | ||
| class LocalizableMessage { |
There was a problem hiding this comment.
I didn't end up using this class but I would like to keep it in since I eventually want to do some work towards standardizing our error messages and this would play a part in that. I'm imagining we have either a custom error class or a custom error message class that can hold a localized message and any other data we need.
| * Translates messages into a specific locale. | ||
| */ | ||
| @Component | ||
| class MessageLocalizer { |
There was a problem hiding this comment.
This is meant to standardize how we localize our messages in the app. We have a ton of different approaches and I didn't know which to choose when localizing dates for this PR so got fed up and decided to try to unify them all under one component. Eventually I'd like to unify our errors and GSPs to all localize via the same method so that we can remove the other approaches. That's a refactoring I'll save for a future date but this at least gives us a starting point
| * If you're trying to translate a message, use {@link org.pih.warehouse.core.localization.MessageLocalizer}. | ||
| */ | ||
| @Component | ||
| class LocaleDeterminer { // Note I wanted to call this LocaleResolver but that's already a Spring component |
There was a problem hiding this comment.
I noticed we had a ton of different ways that we resolved locales so I decided that I was going to bite the bullet and try to unify our behaviour.
I'll eventually add a flow diagram to this PR to demonstrate all the components that go into this change since I feel like it's hard to visualize them all. I figured a bunch of single purpose classes gives us better flexibility and testability than a single, massive class that does locale, localization, and messaging all in one place.
Eventually I want to refactor LocalizationUtil and all the other l10n related services as well but that'd be too much for one PR
|
|
||
| import org.pih.warehouse.core.localization.MessageLocalizer | ||
|
|
||
| class LocalizationTagLib { |
There was a problem hiding this comment.
This isn't currently used anywhere. I figured we'd want to experiment with the new messageLocalizer before applying it everywhere. Once we're comfortable that this flow works, we can switch the namespace of this class to 'g' and then everywhere that we have <g:message> will go through this taglib.
| date.format.date.label=dd/MMM/yyyy | ||
| date.format.dateTime.label=dd/MMM/yyyy HH:mm:ss | ||
| date.format.dateTimeZone.label=dd/MMM/yyyy HH:mm:ss XXX | ||
| date.format.time.label=HH:mm:ss |
There was a problem hiding this comment.
As i mentioned on the call last week (?), i would really like to keep these all under the default namespace in messages.properties.
default.date.format=dd/MMM/yyyy
default.time.format=HH:mm:ss
default.date.time.format=dd/MMM/yyyy HH:mm:ss
default.date.time.timezone.format=dd/MMM/yyyy HH:mm:ss XXX
If that's going to cause too big of a change (since default.date.format has always been used to render the full datetime format) then we could preserve that by leaving it alone
default.date.format=dd/MMM/yyyy hh:mm:ss a z
and adding more context-specific formats
default.date.only.format=dd/MMM/yyy
default.datetime.format=dd/MMM/yyyy HH:mm:ss
default.datetime.timezone.format=dd/MMM/yyyy hh:mm:ss a z
default.time.format=HH:mm:ss
and then adding more date formats in the future, as needed if codifying these became necessary.
default.date.expiry.format=MMM yyyy
default.date.pretty.format=EEE, MMM dd, yyyy
default.date.weekday.format=EEE
default.date.mysql.format=yyyy-MM-dd
default.date.filename.format.=yyyyMMdd
default.datetime.filename.format=yyyyMMddHHmmss
✨ Description of Change
Link to GitHub issue or Jira ticket: https://pihemr.atlassian.net/browse/OBPIH-7488
Description: As a part of the timezone fix, we're trying to narrow down to only a specific set of patterns that we use for displaying dates. In addition, we want each of these date formats to be localizable since different languages might display dates differently.
For example, in English we may want to display LocalDate fields like dd/MMM/yyyy but in Chinese we may want to display them like yyyy 年 MM 月 dd 日.
So this ticket does the following:
Ultimately, to localize our fields we do:
📷 Screenshots & Recordings (optional)
Here's a diagram of all the important classes involved:
