Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fesod.sheet.exception;

import java.util.Locale;

/**
* Exception thrown when a message cannot be resolved.
*/
public class NoSuchMessageException extends ExcelRuntimeException {

public NoSuchMessageException(String code, Locale locale) {
super("No message found under code '" + code + "' for locale '" + locale + "'.");
}

public NoSuchMessageException(String code) {
super("No message found under code '" + code + "' for locale '" + Locale.getDefault() + "'.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fesod.sheet.i18n;

import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fesod.sheet.exception.NoSuchMessageException;

/**
* Abstract implementation of the {@link MessageSource} interface,
* providing common functionality for message resolution, lookup, and interpolation.
*/
public abstract class AbstractMessageSource implements MessageSource {

private static final char PREFIX = '{';
private static final char SUFFIX = '}';
private static final char ESCAPE = '\\';

/**
* whether to use the message code as default message.
*/
@Getter
@Setter
private boolean useCodeAsDefaultMessage = false;

/**
* Resolve a message for the given code and locale.
*
* @param code the message code
* @param locale the target locale to use
* @return the resolved message
* @throws NoSuchMessageException if the message is not found
*/
@Override
public String getMessage(String code, Locale locale) throws NoSuchMessageException {
String msg = getMessageInternal(code, locale, new LinkedHashSet<>(4));
if (msg != null) {
return msg;
}
throw new NoSuchMessageException(code, locale);
}

/**
* Interpolate the given message template.
*
* @param messageTemplate the message template
* @param locale the target locale to use
* @return the interpolated message
*/
@Override
public String interpolate(String messageTemplate, Locale locale) {
if (StringUtils.isEmpty(messageTemplate)) {
return messageTemplate;
}
return replaceParameters(messageTemplate, locale, new LinkedHashSet<>(4));
}

/**
* Resolve the raw message template for the given code.
*
* @param code the message code
* @param locale the locale to use
* @return the raw message
*/
protected abstract String doResolveCode(String code, Locale locale);

private String getRawMessage(String code, Locale locale) {
if (code == null) {
return null;
}
return doResolveCode(code, locale == null ? Locale.getDefault() : locale);
}

private String getMessageInternal(String code, Locale locale, Set<String> visitedParameters) {
String msg = getRawMessage(code, locale);
if (msg != null) {
return replaceParameters(msg, locale, visitedParameters);
}
return isUseCodeAsDefaultMessage() ? code : null;
}

private String replaceParameters(String message, Locale locale, Set<String> visitedParameters) {
if (StringUtils.isEmpty(message)) {
return message;
}

StringBuilder buf = new StringBuilder(message);
int startIndex = -1;

for (int i = 0; i < buf.length(); i++) {
char c = buf.charAt(i);

// Handle redundant escape characters
if (c == ESCAPE && i + 1 < buf.length()) {
char next = buf.charAt(i + 1);
if (next == PREFIX || next == SUFFIX || next == ESCAPE) {
buf.deleteCharAt(i);
continue;
}
}
if (c == PREFIX) {
if (startIndex != -1) {
throw new IllegalArgumentException(
String.format("The message template '%s' contains nested parameters", message));
}
startIndex = i;
} else if (c == SUFFIX && startIndex != -1) {
int endIndex = i;
String parameter = buf.substring(startIndex + 1, endIndex);

if (!visitedParameters.add(parameter)) {
throw new IllegalArgumentException("Circular reference detected: '{"
+ String.join(" -> ", visitedParameters) + " -> " + parameter + "}'");
}

try {
String value = getMessageInternal(parameter, locale, visitedParameters);

if (value != null) {
buf.replace(startIndex, endIndex + 1, value);
i = startIndex + value.length() - 1;
}
} finally {
visitedParameters.remove(parameter);
}

startIndex = -1;
}
}
return buf.toString();
}

@EqualsAndHashCode
@AllArgsConstructor
protected static final class CachedMessageKey {
final Locale locale;
final String code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fesod.sheet.i18n;

import java.util.Locale;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
* Localization context for i18n.
*/
@Getter
@RequiredArgsConstructor
public class LocalizationContext {

public static final LocalizationContext DEFAULT = new LocalizationContext(MessageSource.PASS, Locale.getDefault());

/**
* The message source providing message resolution.
*/
private final MessageSource messageSource;

/**
* The locale to use to resolve messages.
*/
private final Locale locale;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fesod.sheet.i18n;

import java.util.Locale;

/**
* Strategy interface for interpolating message templates.
*/
public interface MessageInterpolator {

/**
* Interpolate the given message template.
*
* @param messageTemplate the message template
* @param locale the target locale to use
* @return the interpolated message
*/
String interpolate(String messageTemplate, Locale locale);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fesod.sheet.i18n;

import java.util.Locale;
import org.apache.fesod.sheet.exception.NoSuchMessageException;

/**
* Strategy interface for resolving messages.
*/
public interface MessageSource extends MessageInterpolator {

MessageSource PASS = new MessageSource() {
@Override
public String getMessage(String code, Locale locale) throws NoSuchMessageException {
return code;
}

@Override
public String interpolate(String messageTemplate, Locale locale) {
return messageTemplate;
}
};

/**
* Resolve a message for the given code and locale.
*
* @param code the message code
* @param locale the target locale to use
* @return the resolved message
* @throws NoSuchMessageException if the message is not found
*/
String getMessage(String code, Locale locale) throws NoSuchMessageException;
}
Loading
Loading