<a id="lnkNo1" href="#" rel="tooltip" title="some text here">some text here too!</a>
$('#lnkNo1').tooltip({ selector: '[rel=tooltip]' });
<a id="lnkNo1" href="#" rel="tooltip" title="some text here">some text here too!</a>
$('#lnkNo1').tooltip({ selector: '[rel=tooltip]' });
import Pagination from "./common/pagination";
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} />
state = { movies: getMovies(), pageSize: 4 };
handlePageChange = page => { console.log("handlePageChange", page); };
import React, { Component } from "react"; class Pagination extends Component { render() { return ( <nav> <ul className="pagination"> <li className="page-item"> <a className="page-link">1</a> </li> </ul> </nav> ); } } export default Pagination;
class Pagination extends Component { // ... getPageNumbersArray() { const { itemsCount, pageSize } = this.props; const pagesCount = Math.ceil(itemsCount / pageSize); if (pagesCount === 1) { return null; } const pages = new Array(); for (let i = 1; i <= pagesCount; i++) { pages.push(i); } return pages; } }
class Pagination extends Component { render() { const pages = this.getPageNumbersArray(); if (!pages) { return null; } return ( <nav> <ul className="pagination"> {pages.map(page => ( <li key={page} className="page-item"> <a className="page-link">{page}</a> </li> ))} </ul> </nav> ); }
<a onClick={() => this.props.onPageChange(page)} className="page-link" style={{ cursor: "pointer" }} > {page} </a>
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} currentPage={this.state.currentPage} />
class Movies extends Component { state = { movies: getMovies(), pageSize: 4, currentPage: 1 };
handlePageChange = page => { console.log("handlePageChange", page); this.setState({currentPage: page}); };
<li key={page} className={ page === this.props.currentPage ? "page-item active" : "page-item" } >
paginate() { const first = (this.state.currentPage - 1) * this.state.pageSize; const last = first + this.state.pageSize; return this.state.movies.slice(first, last); }
render() { const { length: count } = this.state.movies; if (count === 0) return <p>There are no movies in the database.</p>; const movies = this.paginate();
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha
> npm i prop-types --save
C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index
import PropTypes from "prop-types";
Pagination.propTypes = { itemsCount: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired }; export default Pagination;
البته این خطا فقط در حالت development مشاهده میشود و در حالت توزیع برنامه، خیر.
طراحی مدل دیتابیس
اولین کار برای داشتن یک سایت چند زبانه، این است که یک مدل صحیح و مناسب را برای دیتابیس خود انتخاب کنید. یکی از اولین روشهایی که به ذهن هر فردی میرسد این است که برای هر ستون متنی که قرار است چند زبانه باشد، به تعداد زبانها برایش یک ستون در نظر بگیریم. یعنی برای جدول مقالات که قرار است در سه زبان فارسی و انگلیسی و عربی باشد، سه ستون برای عنوان مقاله و سه ستون نیز برای متن آن داشته باشیم. تصویر زیر نمونهای از این مدل را نشان میدهد.
مزایا:
معایب:
پی نوشت: با اینکه امروزه بحث فیلدهای sparse Column وجود دارد ولی این فیلدها در هر شرایطی مورد استفاده قرار نمیگیرند وبیشتر متعلق به زمانی است که میدانیم آن فیلد به شدت کم مورد استفاده قرار میگیرد.
پی نوشت دوم : در صورتی که فیلد شما مانند متن مقاله که عموما از نوع داده (varchar(max است استفاده میکنید و در صورتی که زبان مورد استفاده قرار نگیرد در خیلی از اوقات بی جهت فیلدهای Blob ساخته اید که بهینه سازی آن را نیز باید در نظر بگیرید.
ID | کد |
Language | زبان |
ISO | کد دو رقمی آن زبان |
Flag | پرچم آن کشور |
مزایا:
معایب:
سومین راه حل این است که سه جدول ایجاد کنیم:
یک. جدول زبانها (که بالاتر ایجاد شده بود)
دو . جدول نام مقاله به همراه اطلاعات پایه و فیلدها بی نیاز به چند زبانه بودن
سه : یک جدول که هر دو ستون آن کدهای کلید دو جدول بالا را دارند و فیلدهای چند زبانه در آن وجود دارند.
جدول پایه
ID | کد |
Name | نام مقاله |
CreationDate | تاریخ ایجاد |
Writer | نویسنده |
Visibilty | وضعیت نمایش |
LanguageCode | کد زبان |
ArticleID | کد مقاله |
CreationDate | تاریخ ایجاد |
Visibility | وضعیت نمایش مقاله |
Title | عنوان مقاله |
ContentText | متن مقاله |
در جدول پایه یک مقاله ایجاد میشود که اطلاعات عمومی همه مقالات را دارد و حتی خصوصیت وضعیت نمایش آن، روی همهی مقالات با هر زبانی تاثیر میگذارد. در جدول دو، هر مقاله یک رکورد دارد که کد زبان و کد مقاله برای آن یک کلید ترکیبی به حساب میآیند. پس، از هر مقاله یک یا چند زبان خواهیم داشت. همچنین دارای فیلدهایی با وضعیت مخصوص به خود هم هستند؛ مثل فیلد وضعیت نمایش مقاله که فقط برای این مقاله با این زبان کاربرد دارد.
مزایا:
معایب:
استفاده از ساختارهای XML یا JSON برای ذخیره سازی اطلاعات چند زبانه مانند ساختارهای زیر:
XML<Articles> <Article> this is english text </Article> <Article> این یک متن فارسی است </Article> </Articles> یا <Articles> <en-us> this is english text </en-us> <fa-ir> این یک متن فارسی است </fa-ir> </Articles>
"Articles":["en-us':{"title":"this is english text","content":" english content"},"fa-ir":{"title":"متن فارسی","content":"محتوای فارسی"}]
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<blockquote lang=”fr”> <p>Le plus grand faible des hommes, c'est l'amour qu'ils ont de la vie.</p> </blockquote>
<a href="" hreflang="fr">French</a>
<a href="" hreflang="fr">Francais</a>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta charset="UTF-8">
:lang(en) { font-size: 85%; font-family: arial, verdana, sans-serif; } :lang(zh) { font-size: 125%; font-family: helvetica, verdana, sans-serif; }
<html lang="en">
<html lang="zh">
<body class="english"> or <body class="chinese">
.english { font-size: 85%; font-family: arial, verdana, sans-serif; } .chinese { font-size: 125%; font-family: helvetica, verdana, sans-serif; }
هشت : زمان را نیز تغییر دهید: یکی از مواردی که در کمتر سایت چند زبانهای به چشم میخورد و به نظر بنده میتواند بسیار مهم باشد این است که time zone منطقهی هر زبان را بدانید. به عنوان مثال برای مقالهی خود، تاریخ ایجاد را به صورت UTC ذخیره کنید و سپس نمایش را بر اساس زبان یا حتی بهتر و دقیقتر از طریق IP کشور مربوطه به دست آورید. برای کاربران ثبت نام شده این تاریخ میتواند دقیقتر باشد همانند انجمنهای وی بولتین.
شیوههای تشخیص زبان سایت
یکی از راههای تشخیص زبان این است که موقعی که برای اولین بار کاربری به سایت مراجعه میکند، زبان مورد نظرش را سوال کنید و این اطلاعات را در یک کوکی بدون تاریخ انقضاء ذخیره کنید تا در دفعات بعدی آن را بررسی نمایید.
دومین راه، استفاده از IP کاربر مراجعه کننده است تا بر اساس آن زبان مورد نظر را انتخاب کنید.
در سومین شیوه که اغلب استفاده میشود، زبان سایت به طور پیش فرض بر روی یک زبان خاص که بهتر است انگلیسی باشد تنظیم شده است و سپس کاربر از طریق یک منو یا ابزارهای موجود در سایت، زبان سایت را تغییر دهد.
پی نوشت: فراموش نگردد که امکان تغییر زبان همیشه برای کاربر مهیا باشد و طوری نباشد که کاربر در آینده نتواند زبان سایت را تغییر دهد؛ حتی اگر تشخیص خودکار سایت برای زبان فعال باشد.
پلاگینها و ابزارهای مدیریت زبانپی نوشت: در روشهای بالا بهتر است همان مرتبهی اول اطلاعات را در یک کوکی ذخیره کنید تا مراحل پیگیری راحتتر و آسانتر شود.
در مقالههای زیر که در سایت جاری است در مورد Globalization و به خصوص استفاده از ریسورسها مطالب خوبی بیان شده است:
قسمت بیست و دوم آموزش MVC که مبحث Globalization را دنبال میکند.
قسمت اول از شش قسمت مباحث Globalization که دنبالهی آن را میتوانید در مقالهی خودش دنبال کنید.
<script src="file.js" defer></script>
<script src="file.js" async></script>
تصویر فوق یک سناریوی سادهی بارگذاری صفحهی html را به همراه یک فایل جاوااسکریپت با تگ script ساده را نمایش میدهد. همانطور که ملاحظه میکنید، در لحظهای که فایل اسکریپت شروع به دانلود شدن میکند (قسمت بنفش)، عملیات parse صفحه متوقف شده تا زمانی که فایل دانلود و اجرا شود (قسمت قرمز). پس از آن دوباره عملیات parsing از سر گرفته میشود. این عملیات به همراه تگ script ساده صورت گرفته است. حال در تصویر بعد قصد داریم که همین سناریو را به کمک defer Attribute بررسی کنیم:
همانطور که ملاحظه میکنید به کمک defer، شروع عملیات دانلود فایل اسکریپت، خللی در عملیات html parse ایجاد نمیکند و این دو عملیات به صورت موازی میتوانند اجرا شوند. پس از دریافت فایل اسکریپت، این فایل مستقیما اجرا نمیشود و لزوما باید پس از عملیات parsing صورت پذیرد.
و اما تصویر زیر، سناریوی فوق را به همراه async attribute نمایش میدهد:
در این حالت عملیات دریافت فایل اسکریپت همانند defer به صورت موازی اجرا میشود. اما تفاوتی که در این قسمت و در async وجود دارد، زمان اجرا شدن فایلهای اسکریپت است. همانطور که ملاحظه میکنید دقیقا پس از دریافت فایل، اسکریپت ما در اجراست و پس از اجرای کامل، دوباره عملیات parsing ادامه مییابد. در حقیقت اسکریپتهای با async attribute با فراخوانی رخداد onload شروع به اجرا میکنند. جهت استفادهی از async، خیالتان راحت باشد؛ با توجه به این منبع، تقریبا تمام مرورگرها از این ویژگی پشتیبانی میکنند.
<script type="text/javascript">
$(document).ready(function() {
$("a").click(function() {
var $a = $(this);
var href = $a.attr("href");
// see if the link is external
if ( (href.match(/^http/)) && (! href.match(document.domain)) ) {
// if so, register an event
var category = "outgoing";
var event = "click";
var label = href;
pageTracker._trackPageview('/outgoing/' + href);
pageTracker._trackEvent(category, event, href);
}
});
});
</script>
[TestMethod] public void SearchForWatiNOnGoogle() { using (var browser = new IE("http://www.google.com")) { browser.TextField(Find.ByName("q")).TypeText("WatiN"); browser.Button(Find.ByName("btnG")).Click(); Assert.IsTrue(browser.ContainsText("WatiN")); } }
[TestMethod] public void SearchForWatiNOnGoogle() { using (var browser = new IE("http://www.google.com")) { browser.TextField(Find.ByName("q")).Value="WatiN"; browser.Button(Find.ByName("btnG")).ClickNoWait(); Thread.Sleep(3000); Assert.IsTrue(browser.ContainsText("WatiN")); } }
www.mysite.com/myapp
<!DOCTYPE html> <html> <head> <base href="/">
<base href="/myapp/">
> ng build --base-href /myapp/
> ng new angular-routing-lab --routing
> npm install bootstrap --save
"apps": [ { "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', children: [] } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
import { AppRoutingModule } from './app-routing.module'; @NgModule({ imports: [ AppRoutingModule ],
@NgModule({ imports: [RouterModule.forRoot(routes)],
<h1> {{title}} </h1> <router-outlet></router-outlet>
>ng g c welcome >ng g c PageNotFound
@NgModule({ declarations: [ AppComponent, WelcomeComponent, PageNotFoundComponent ],
import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; import { WelcomeComponent } from './welcome/welcome.component'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: 'welcome', component: WelcomeComponent }, { path: '', redirectTo: 'welcome', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
{ path: 'welcome', component: WelcomeComponent },
{ path: '', redirectTo: 'welcome', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent },
const routes: Routes = [ { path: 'home', component: WelcomeComponent }, { path: 'welcome', redirectTo: 'home', pathMatch: 'full' }, { path: '', redirectTo: 'welcome', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ];
const routes: Routes = [ { path: 'home', component: WelcomeComponent }, { path: 'welcome', redirectTo: 'home', pathMatch: 'full' }, { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ];
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/home']">Home</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
@Component({ //selector: 'app-welcome', templateUrl: './welcome.component.html', styleUrls: ['./welcome.component.css'] })
<a [routerLink]="['/home']">Home</a>
<a routerLink="/home">Home</a>
<rewrite> <rules> <rule name="Angular 2+ pushState routing" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" /> <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite>
@NgModule({ imports: [RouterModule.forRoot(routes, { useHash: true })],