04.10.2025

Rails 7 with Svelte: Step-by-Step Setup

Dariusz Michalski

CEO

Learn how to seamlessly integrate Rails 7 with Svelte for a fast, modern web application that caters to Swiss localization needs.

<h1>Svelte {title}</h1>

// Format currency in CHF
const formatCurrency = (amount) => {
return new Intl.NumberFormat('de-CH', {
style: 'currency',
currency: 'CHF'
}).format(amount);
};

// Format date to Swiss style
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('de-CH', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};

// Format temperature in Celsius
const formatTemperature = (temp) => {
return ${temp}°C;
};
</script>

<div class="user-profile">
<h2>{name}</h2>
<p><strong>Member since:</strong> {formatDate(joinDate)}</p>
<p><strong>Account balance:</strong> {formatCurrency(accountBalance)}</p>
<p><strong>Local temperature:</strong> {formatTemperature(temperature)}</p>
</div>

<style>
.user-profile {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
background-color: #f9f9f9;
}

.user-profile h2 {
margin-top: 0;
color: #333;
}

.user-profile p {
margin: 0.5rem 0;
}
</style>

private

def fetch_local_temperature
# Fetch from a weather API
22.5
end
end

<%= svelte_component('UserProfile', @user_data) %>

<p>Additional Rails content can go here...</p>
</div>

const increment = () => {
count += 1;
};

const decrement = () => {
count -= 1;
};
</script>

<div class="counter">
<button on:click={decrement}>-</button>
<span class="count">{count.toLocaleString('de-CH')}</span>
<button on:click={increment}>+</button>
</div>

const components = {
ProductCard
};

export function registerSvelteComponents() {
Object.keys(components).forEach(key => {
const elements = document.querySelectorAll([data-svelte-component='${key}']);

elements.forEach(element =&gt; {
  const propsData = element.dataset.svelteProps;
  const props = propsData ? JSON.parse(propsData) : {};

  new components[key]({
    target: element,
    props: props
  });
});

});
}

render json: {
  name: product.name,
  price: product.price,
  formatted_price: helpers.number_to_currency(product.price, unit: 'CHF ', precision: 2, delimiter: &quot;'&quot;),
  updated_at: product.updated_at.strftime('%d.%m.%Y %H:%M')
}

end
end

export let productId;
let product = null;
let loading = true;
let error = null;

onMount(async () => {
try {
const response = await fetch(/api/products/${productId}, {
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});

  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

  product = await response.json();
} catch (e) {
  error = e.message;
} finally {
  loading = false;
}

});
</script>

{#if loading}
<p>Loading product information...</p>
{:else if error}
<p class="error">Error: {error}</p>
{:else if product}
<div class="product-info">
<h3>{product.name}</h3>
<p class="price">{product.formatted_price}</p>
<small>Last updated: {product.updated_at}</small>
</div>
{/if}

<%= svelte_component('ProductFilter', props: {
categories: @categories.map(&:name),
price_range: { min: 0, max: 5000 }
}) %>

<div id="products-container">
<%= render partial: 'product', collection: @products %>
</div>
</div>

export function formatNumber(number, locale = 'de-CH') {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(number);
}

export function formatDate(date, locale = 'de-CH') {
return new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}).format(new Date(date));
}

export function formatDateTime(date, locale = 'de-CH') {
return new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(new Date(date));
}

export let product;
export let locale = 'de-CH'; // Default to German-Swiss

$: formattedPrice = formatCurrency(product.price, locale);
$: formattedDiscount = product.discount ? formatNumber(product.discount, locale) + '%' : null;
$: formattedDate = formatDate(product.created_at, locale);
</script>

<div class="product-card">
<h3>{product.name}</h3>
<div class="price-info">
<span class="current-price">{formattedPrice}</span>
{#if formattedDiscount}
<span class="discount">-{formattedDiscount}</span>
{/if}
</div>
<p class="product-details">
Weight: {formatNumber(product.weight_kg, locale)} kg<br>
Added: {formattedDate}
</p>
</div>

export const currentLocale = writable('de-CH');

return ${formatted} °C;
}

export function formatDistance(metres, locale = 'de-CH') {
if (metres >= 1000) {
const km = metres / 1000;
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 2
}).format(km) + ' km';
}

return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(metres) + ' m';
}

export function formatWeight(grams, locale = 'de-CH') {
if (grams >= 1000) {
const kg = grams / 1000;
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 2
}).format(kg) + ' kg';
}

return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(grams) + ' g';
}

export let weatherData;

$: temperature = formatTemperature(weatherData.temperature, $currentLocale);
$: visibility = formatDistance(weatherData.visibility_metres, $currentLocale);
</script>

<div class="weather-widget">
<div class="temperature">
<span class="value">{temperature}</span>
<span class="condition">{weatherData.condition}</span>
</div>
<div class="details">
<p>Visibility: {visibility}</p>
<p>Humidity: {weatherData.humidity}%</p>
</div>
</div>

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

unser Büro

ul. Ofiar Oświęcimskich 17

50-069 Wrocław, Poland

©2009 - 2025 Useo sp. z o.o.

unser Büro

ul. Ofiar Oświęcimskich 17

50-069 Wrocław, Poland

©2009 - 2025 Useo sp. z o.o.