Oct 4, 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>

Have a project idea? Let's talk and bring it to life

Your highly qualified specialists are here. Get in touch to see what we can do together.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Have a project idea? Let's talk and bring it to life

Your highly qualified specialists are here. Get in touch to see what we can do together.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Have a project idea? Let's talk and bring it to life

Your highly qualified specialists are here. Get in touch to see what we can do together.

Start a Project
our Office

ul. Ofiar Oświęcimskich 17

50-069 Wrocław, Poland

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

Start a Project
our Office

ul. Ofiar Oświęcimskich 17

50-069 Wrocław, Poland

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