Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Translations are now consumed from the central `dev.escalated:escalated-locale` Maven artifact via a chained `ReloadableResourceBundleMessageSource`. Host apps can layer sparse overrides under `classpath:i18n/overrides/messages_{locale}.properties`.

### Fixed
- Make `SimpMessagingTemplate` an optional dependency so the app boots without STOMP broker configuration (#19)
- Include `url` in attachment JSON serialization (#10)
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ spring.flyway.enabled=true

Flyway migrations are included and run automatically. The migration creates all tables prefixed with `escalated_` and seeds default roles and permissions.

## Internationalization

Translations are consumed from the central [`dev.escalated:escalated-locale`](https://github.com/escalated-dev/escalated-locale) Maven artifact, which ships bundles at `META-INF/escalated/locale/messages_{locale}.properties` on the classpath. The auto-configured `MessageSource` chains two basenames so host apps can override keys without forking the central bundle:

1. `classpath:i18n/overrides/messages` — sparse host-app overrides (first match wins)
2. `classpath:META-INF/escalated/locale/messages` — central artifact (canonical strings)

Drop a `messages_{locale}.properties` file under `src/main/resources/i18n/overrides/` to override individual keys. See [`src/main/resources/i18n/overrides/README.md`](src/main/resources/i18n/overrides/README.md) for examples. To fix a typo or mistranslation that affects every host plugin, open a PR against the central [`escalated-locale`](https://github.com/escalated-dev/escalated-locale) repo instead.

## Inbound email

Point your Postmark, Mailgun, or AWS SES (via SNS HTTP subscription) inbound webhook at:
Expand Down
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-autoconfigure")

// Central translations artifact. Ships messages_{locale}.properties at
// META-INF/escalated/locale/ on the classpath. Layered with local overrides
// in the classpath:i18n/overrides/ directory via a HierarchicalMessageSource.
implementation("dev.escalated:escalated-locale:0.1.0")

implementation("org.flywaydb:flyway-core")
implementation("com.fasterxml.jackson.core:jackson-databind")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

Expand All @@ -15,5 +16,6 @@
@EntityScan(basePackages = "dev.escalated.models")
@EnableJpaRepositories(basePackages = "dev.escalated.repositories")
@EnableScheduling
@Import(MessageSourceConfig.class)
public class EscalatedAutoConfiguration {
}
59 changes: 59 additions & 0 deletions src/main/java/dev/escalated/config/MessageSourceConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dev.escalated.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

import java.nio.charset.StandardCharsets;

/**
* Wires the helpdesk's {@link MessageSource} so translation strings come from
* the central <code>dev.escalated:escalated-locale</code> Maven artifact, with
* an optional local override layer.
*
* <p>Resolution order (first match wins):
* <ol>
* <li><code>classpath:i18n/overrides/messages</code> -- host-app overrides
* checked into <code>src/main/resources/i18n/overrides/</code>.</li>
* <li><code>classpath:META-INF/escalated/locale/messages</code> -- shipped by
* the central <code>escalated-locale</code> artifact.</li>
* </ol>
*
* <p>The central artifact is assumed to package its bundles under
* <code>META-INF/escalated/locale/messages_{locale}.properties</code>. This
* mirrors the convention used by other Escalated host plugins and avoids
* collisions with the host app's own <code>messages.properties</code>.
*/
@Configuration
public class MessageSourceConfig {

/**
* Central artifact basename. The artifact ships its bundles at
* <code>META-INF/escalated/locale/messages_{locale}.properties</code>.
*/
private static final String CENTRAL_BASENAME =
"classpath:META-INF/escalated/locale/messages";

/**
* Local override basename. Drop a <code>messages_{locale}.properties</code>
* file under <code>src/main/resources/i18n/overrides/</code> in the host
* app to override individual keys without forking the central artifact.
*/
private static final String OVERRIDE_BASENAME =
"classpath:i18n/overrides/messages";

@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource source =
new ReloadableResourceBundleMessageSource();
// Order matters: overrides resolved before the central bundle.
source.setBasenames(OVERRIDE_BASENAME, CENTRAL_BASENAME);
source.setDefaultEncoding(StandardCharsets.UTF_8.name());
source.setFallbackToSystemLocale(false);
// Cache forever; translations are immutable jar resources.
source.setCacheSeconds(-1);
source.setUseCodeAsDefaultMessage(true);
return source;
}
}
Empty file.
45 changes: 45 additions & 0 deletions src/main/resources/i18n/overrides/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Local translation overrides

Drop `messages_{locale}.properties` files in this directory to override
individual translation keys shipped by the central
[`dev.escalated:escalated-locale`](https://github.com/escalated-dev/escalated-locale)
Maven artifact.

## Resolution order

Spring's `MessageSource` walks the chain configured in
[`MessageSourceConfig`](../../java/dev/escalated/config/MessageSourceConfig.java)
in order. The first basename to resolve a key wins:

1. `classpath:i18n/overrides/messages` — files in **this** directory
2. `classpath:META-INF/escalated/locale/messages` — shipped by the central artifact

Any key you do *not* redefine here continues to resolve from the central
bundle, so override files can be sparse.

## Example

To rename `status.open` to "Active" for English only:

```properties
# src/main/resources/i18n/overrides/messages_en.properties
status.open=Active
```

## Conventions

- Use the same key names as the central artifact (`status.*`, `priority.*`,
`activity.*`, …).
- Keep override files **sparse** — only the keys you actually want to change.
- Filename uses Java's `Locale` convention: `messages_en.properties`,
`messages_pt_BR.properties`, etc.
- UTF-8 encoded.

## When NOT to use overrides

If a translation in the central artifact is wrong (typo, mistranslation),
open a PR against [`escalated-locale`](https://github.com/escalated-dev/escalated-locale)
instead — every host plugin benefits from the fix.

Reserve this directory for host-app branding adjustments ("Open" → "Active",
"Ticket" → "Case", etc.).
Loading